Slide 1

Slide 1 text

© GO Inc. 成長期における、 ユーザー領域の複雑さと 整備の進め方 PHPerKaigi 2026 P山

Slide 2

Slide 2 text

© GO Inc. 2 @pyama86 GO株式会社 バックエンド開発部 / pyama86 2014年よりGMOペパボ株式会社でホスティング事業や 技術部で主にプラットフォームエンジニアリングに従事。 2025年よりGO株式会社においてバックエンド開発。 趣味は旅行、キャンプ、ハードワーク

Slide 3

Slide 3 text

© GO Inc. 『GO』アプリの前身である『日本交通タクシー配車』が GPSを活用したタクシーアプリとして 2011年に誕生したのがはじまり。 2020年にJapanTaxiとDeNAのMOV/DRIVE CHART事業が統合し Mobility Technologiesが誕生。 その後、GO株式会社へ社名変更。今は『 GO』アプリを起点に、移動の社会課題解決を目指す多様な事業を手掛けている。 2011 2012 2013 2015 2016 2017 2018 2019 2020 2021 2022 3 沿革 2023 2013/11 「Uber」ハイヤー 配車スタート 2018/9 「DiDi」 大阪でスタート 2019/4 「S.RIDE」 東京でスタート 2015年8月 JapanTaxiに 商号変更 2017年3月 「JapanTaxi Wallet」 日本初の到着前決済 2018年12月 『MOV』へ リニューアル 2018年4月 『タクベル』 神奈川でスタート 2021年11月 『GO BUSINESS』 リリース 2020年9月 『GO』 全国 11エリアでスタート 2014 2020年4月 JapanTaxiとDeNAの MOV/DRIVE CHART事業が統合 し Mobility Technologiesに商号変 更 2023年4月 GO株式会社に商号変更 ゴールドマン・サックスより 100億円資金調達 2022年12月 「タクシー産業 GXプロ ジェクト」開始 2019年6月 『DRIVE CHART』 提供開始 2011年1月 『日本交通タクシー配車』 東京でスタート 日本初のタクシーアプリ 2023年10月 チップ機能、 『GO CALL Pro』 開始 2023年3月 『GO Reserve』 『GO Crew』開始 2012年12月 『日本交通タクシー配車』 日本初のネット決済 2023年12月 『GOジョブ』開始 2023年12月 「ニセコモデル」 開始 2016年7月 フリークアウトとの 合弁会社株式会社 IRIS設 立 2017年6月 トヨタ自動車・未来創生 ファンドより資金調達 2023年10月 フィデリティ・インターナショナル ・両備グループから資金調達 2023年11月 May Mobilityに出資 2023年11月 インバウンド対応、 複数台配車機能 開始 2023年12月 フリークアウト・ホールディ ングスから資金調達 2024年1月 株式上場準備開始 2024年4月 「日本版ライドシェア」 対応開始 2024 2024年7月 『GO Charge』開始 2024年12月 Waymo、日本交通と自動運 転技術のテストに向けて戦 略的パートナーシップ締結 2025年8月 GOドライブ株式会社 設立 2025年9月 GOジョブ株式会社 設立 2025 2025年10月 『GO』 全国 47都道府県に拡 大

Slide 4

Slide 4 text

© GO Inc. ※   は当社の登録商標です。 4 タクシーアプリ『 GO』の事業成長  ー ダウンロード数推移 ー 2022年9月 1000万ダウンロード突破! 2021年11月 法人向けタクシー配車管理 『GO BUSINESS』リリース 2021年10月 500万ダウンロード突破! 2020年4月 Mobility Technologies誕生! 2023年4月 「GO株式会社」に商号変更 『GO』累積ダウンロード数 2020年9月 タクシーアプリ『 GO』 全国11エリアでスタート ダウンロード数 (26年1月) 3500万 利用可能エリア 47都道府県 ネットワーク事業者数 1100社以上 年間実車数 (22年6月-23年5月) 6000万回 No1※タクシーアプリとして成長中 ※Sensor Tower調べ - タクシー配車関連アプリにおける、日本国内ダウンロード数( App Store/Google Play合算値) - 調査期間: 2020年10月1日~2025年12月31日 2024年4月 2000万ダウンロード突破! 2024年10月 2500万ダウンロード突破! 2026年1月 3500万ダウンロード突破! 2025年7月 3000万ダウンロード突破!

Slide 5

Slide 5 text

© GO Inc.

Slide 6

Slide 6 text

© GO Inc. 「タクシー GOから朝4時に通知来たんだけど・・・」 ㊙

Slide 7

Slide 7 text

© GO Inc. 7 二つの会社が 合併して、 成長し続けている

Slide 8

Slide 8 text

© GO Inc. プロジェクトのアーキテクチャ クリーンアーキテクチャを参考にした独自の命名規則と呼び出しフロー

Slide 9

Slide 9 text

© GO Inc. このプロジェクト (Go) 一般的なCAの呼び名 責務 handler/ Controller層 HTTPリクエスト処理 controller/ UseCase層 ビジネスロジックの中核 usecase/ Interface Adapter層 DB操作の橋渡し domain/model Entity層 ドメインモデル domain/service Domain Service層 複数controller間の調整 infra/ Infrastructure層 Repository実装 独自の命名規則と責務

Slide 10

Slide 10 text

© GO Inc. 第一の整備 バリデーションの散在解消 仕様の根拠を明確にし、「どこを読めばわかるか」を作る

Slide 11

Slide 11 text

© GO Inc. サービスが成長する過程で、ユーザーのバリデーション処理※が 複数のhandlerに重複して存在するようになっていた。 配車作成・事前確定運賃 ・AI予約という3種類のhandlerそれぞれに、 ほぼ同様の処理が記述されていた。 ※他にも大量にあるので入社お待ちしてます 第一の整備:バリデーションの散在をリファクタリングする 問題の状態

Slide 12

Slide 12 text

© GO Inc. 第一の整備:バリデーションの散在をリファクタリングする uc := controller.NewUsereController(logger, h.MainDB, h.ReadOnlyDB, cacheRepo) user, err := uc.GetDetails(userID) if err != nil { ... } // 安心見守り ansin, saikou, err := h.AnsinDaijoubuController.Validate( h.Ctx, user, payload.IsGroupTypeA(), payload.IsGroupTypeB(), ) // 海外ユーザー if err, ok := h.OverSea(userID); ok { ... } …が永遠に続く ユーザーのバリデーションがそれぞれの handlerに存在

Slide 13

Slide 13 text

© GO Inc. 第一の整備:バリデーションの散在をリファクタリングする なぜこうなるのか? サービスの成長 でユーザーの属性や付属機能は増えつづける https://go.goinc.jp/mimamori

Slide 14

Slide 14 text

© GO Inc. 第一の整備:バリデーションの散在をリファクタリングする uc := controller.NewUsereController(logger, h.MainDB, h.ReadOnlyDB, cacheRepo) user, err := uc.GetDetails(userID) if err != nil { ... } // 安心見守り ansin, saikou, err := h.AnsinDaijoubuController.Validate( h.Ctx, user, payload.IsGroupTypeA(), payload.IsGroupTypeB(), ) // 海外ユーザー if err, ok := h.OverSea(userID); ok { ... } …が永遠に続く 人は同じところにコードを書き足しがち 元々は一つしかなかった バリデーションも足して 書かれてく

Slide 15

Slide 15 text

© GO Inc. domain/service を取り入れた 標準的なフロー コードベースの大半は、単一のビジネスロジックを 実行するシンプルなフローを採用 複雑なフロー (domain/service 経由) 複数の controller を組み合わせる必要がある複 雑なロジックに対しては、調整役として domain/service を追加 handler → controller → usecase → infra handler → domain/service → controller → usecase → infra

Slide 16

Slide 16 text

© GO Inc. 第一の整備:バリデーションの散在をリファクタリングする type UserValidationService struct { userController UserControllerInterface // ... } func (s *UserValidationService) ValidateUserForCarRequest(ctx context.Context, userID uint) (*UserValidationResult, error) { user, err := s.userController.Show(userID) if err != nil { return nil, err } if err := s.validateUserLimitations(user); err != nil { return nil, err } } domain/serviceでシュッとまとめる

Slide 17

Slide 17 text

© GO Inc. 第一の整備:バリデーションの散在をリファクタリングする domain/serviceは便利だが 1. 使いすぎると、Fatなdomain/serviceが増え、 逆に見通しが悪くなるのでポリシーを書いた 2. 既存のアーキテクチャの課題や処理の重複などは 入社直後が一番見えるので、同質化する前 に 直すのが大事 3. 成長サービスはリリース優先とか言いがち だが、 なんと、成長しなくてもリリースは優先される ので リファクタリングは今やらないと一生やらない

Slide 18

Slide 18 text

© GO Inc. 第二の整備 新機能を安全に乗せる 仕組み インターフェースによる仕様のコード化とアクセス制御

Slide 19

Slide 19 text

© GO Inc. 1. 『GO』アプリは複数の認証方法を対応しており、 不正利用の観点における 許容リスクに応じて一部の機能を制限をしています 2. 新しいユーザー認証区分が新設された際に、機能制限をどのように追加するか ユーザー認証区分の追加

Slide 20

Slide 20 text

© GO Inc. ユーザー認証区分の追加 ナイーブに実装するとこうなる // あちこちのhandlerやcontrollerに生えていく if profile.Name == "examle" { limit = 3 } if user.IsExample && !card.Is3DSVerified { return errors.New("3DS認証が必要です") }

Slide 21

Slide 21 text

© GO Inc. 新機能とナイーブな実装の限界 ユーザー認証区分追加の設計時に直面した課題 リスク制御のための制約 新しいユーザー認証区分では 「クレカ3DS必須」「1⽇3件上 限」「エリア限定」「AI予約禁 ⽌」など、機能単位の厳しい制約 を設ける必要があった if文の散在問題 これらの制約を素朴に実装する と、あちこちのhandlerにif user.Exampleという分岐が⽣え、 第⼀の整備と同じ問題が再発して しまいます。 将来の負債化 別の区分追加や法⼈向けプランな ど、新しいユーザー種別が追加さ れるたびに同じパターンが繰り返 され、仕様の全容把握が不可能に なります。

Slide 22

Slide 22 text

© GO Inc. 機能制約 UserCapabilityインターフェース type UserCapability interface { DailyDispatchLimit() *int IsEndpointGroupAllowed(string) bool Is3DSRequired() bool CanUseFamilyLink() bool // ... } 「このユーザーは何ができるか」をインターフェースとして定義することで、 ユーザー種別の仕様をコードとして表現した。 通常ユーザーは DefaultUserCapability 制限ユーザーは LimitedUserCapability 新規ユーザー種別追加時も、既存コードの 変更は不要

Slide 23

Slide 23 text

© GO Inc. ユーザー認証区分の追加 インターフェース化することで変更に強くなった // capability profilesを取得して3DS必須かチェック profiles, err := userCapabilityController.GetActiveProfilesByUserID(userID) if err != nil { return policy, err } if profiles.Is3DSRequired() { // something }

Slide 24

Slide 24 text

© GO Inc. 機能単位のアクセス制御 機能の詳細はインタフェースで処理して、機能グループはエンドポイント制御する 機能単位のグルーピング 200本以上あるAPIエンドポイントを「配⾞」「AI予 約」「貸切」などの機能単位に分類‧グループ化 分類作業にはAIツールを活⽤し、草案を短時間で 作成することで効率的に作業を進めた リクエスト時の自動判定 実⾏時にリクエストの「メソッド」と「パス」から グループ名を動的に解決する仕組みを構築 Capabilityの IsEndpointGroupAllowed に渡し、 エンドポイントが増えてもCapability側の変更が 不要な設計を実現

Slide 25

Slide 25 text

© GO Inc. 機能単位のアクセス制御 Capabilityごとにエンドポイントを定義するだけで権限制御できる // 許可されるべきエンドポイントグループ allowedGroups := []string{ endpointgroup.UserPayment, endpointgroup.UserDispatch, endpointgroup.User, endpointgroup.UserFeedback, } for _, group := range allowedGroups { if !capability.IsEndpointGroupAllowed(group) { t.Errorf("UserCapability should allow endpoint group: %s", group) } }

Slide 26

Slide 26 text

© GO Inc. 第三の整備 トイルの削減と 継続的なDBリファクタリング 同じことを同じようにやり続けない

Slide 27

Slide 27 text

© GO Inc. 1. 設計当初に予見できなかった要素の追加 2. データ量が少なかった時に顕在化しなかったパフォーマンスの問題 3. データレイクに同期するため、差分判定用インデックスを貼る必要が生まれたり トイルの削減 成長に伴い、スキーマの変更が必要になる

Slide 28

Slide 28 text

© GO Inc. 第三の整備: DBスキーマ変更の課題 成長に伴うデータ量増加により、手作業での運用が限界を迎えていた テーブルロックの回避 MySQLの⼤規模テーブルに対する 直接的なALTER TABLEはロックを 引き起こすため、外部ツール (pt-online-schema-change)の 使⽤が不可⽋ 属人化した判断 「通常のALTERでいくか、pt-osc を使うか」の判断基準や実⾏⼿順 が、⼀部のエンジニアの暗黙知に 依存 深夜作業とtoil 変更のたびに⼿順書をコピーし、 担当者2名が深夜2時に集まって⼿ 作業で実⾏するという、典型的な toil(労苦)が常態化

Slide 29

Slide 29 text

© GO Inc. 29 pt-online-schema-change(pt-osc) https://martin-son.github.io/Martin-IT-Blog/mysql/3rd%20party%20tool/pt-osc/2022/08/20/pt-osc-%EC%84%A4%EC%B9%98- %EB%B0%8F-%EC%82%AC%EC%9A%A9%EB%B2%95.htmlより引用 ALTERによるレプリケーション遅延をコントロールして スキーマ・マイグレーションを行うソフトウェア

Slide 30

Slide 30 text

© GO Inc. 見てくれ、歴戦の手順書 $ ssh bastion # 2. 最新スキーマ取得とDryRun $ cd ~/gitrepo/xxxxx-db-schema/ && git pull origin master $ ./mysqldef.sh -m dryrun # 3. 【辛み】pt-osc実行(手作業でのクエリ調整) # 🚨罠:mysqldefの差分クエリの引用符を `'` から `”` に手動変更し、 #    バッククォートも手動削除する必要あり(ヒューマンエラーの温床) $ ./ptosc.sh -m dryrun -t target_huge_table -q 'ADD KEY index_target_huge_table_on_updated_at (updated_at)' $ ./ptosc.sh -m execute -t target_huge_table -q 'ADD KEY ...' # 4. 終わらない監視 $ tail -f /tmp/pt-online-schema-change.log # GrafanaでAPIレスポンス、レプリ遅延、CPU使用率を常時監視...

Slide 31

Slide 31 text

© GO Inc. 31 運用課題を解決するalterguard作った 1. ALTER文を食わせると、対象のテーブルの行数に 応じてALTER実行するか、pt-oscするか自動で決定して実行 2. DryRunやSlack通知を備えており、自動化に特化 3. テーブルのリネームやクリーンアップも対応 a. リネーム時に新旧のテーブル行数を精査 b. 成功時のクリーンアップだけじゃなく、失敗時の クリーンアップも対応 c. スイッチする時はバッファプールから古いテーブルが消えてることを 確認してスイッチする https://github.com/pyama86/alterguard/

Slide 32

Slide 32 text

判断と手順のコード化 • ⾃動実⾏: PRマージ後、CI (GitHub Actions) 経由で対象 テーブルの⾏数を取得し、閾値に応じて最適な⼿法を⾃動 選択。 • 統合と通知: Kubernetes Jobとして実⾏され、完了後は Slackへ⾃動通知。 • toilの削減: 完全に無⼈化はできないものの、変更のたびに 深夜に集まる⼿作業の⼤部分を排除することに成功 第三の整備: alterguardによる自動化

Slide 33

Slide 33 text

散らばるものを集める 複数箇所に重複していたバリデー ションを1箇所に集約。 「ここを読めば仕様がわかる」と いう明確な居場所を作ることで、 認知負荷と変更コストを下げる 安全な抽象を作る UserCapabilityインターフェース の導⼊。 仕様の条件をコードとして抽象化 することで、既存コードを壊さず に次の変更を安全に乗せられる 構造を作る 判断をコードに落とす DB変更ツールalterguardの開発。 ⼈間の暗黙知や繰り返される⼿作 業(toil)を⾃動化し、エンジニア が本来の開発に注⼒できる環境を 整備する 今日話したこと

Slide 34

Slide 34 text

成⻑期のプロダクトを整備する際、 すべてを⼀度に直す必要はない まず「居場所を作る」こと、そして 「次の変更が乗せやすい抽象を置く」ことを 繰り返す — プロダクトは徐々に変えやすい状態へと近づいていく— " " 継続的な改善

Slide 35

Slide 35 text

© GO Inc. いま、もっともアツい夏がここにあります 35 私たちと一緒に 未来を作っていきませんか?