Upgrade to Pro — share decks privately, control downloads, hide ads and more …

成長期における、 ユーザー領域の複雑さと 整備の進め方

Avatar for Kazuhiko Yamashita Kazuhiko Yamashita
March 21, 2026
26

成長期における、 ユーザー領域の複雑さと 整備の進め方

PHPerKaigi 2026でお話ししました。。

Avatar for Kazuhiko Yamashita

Kazuhiko Yamashita

March 21, 2026
Tweet

More Decks by Kazuhiko Yamashita

Transcript

  1. © GO Inc. 2 @pyama86 GO株式会社 バックエンド開発部 / pyama86 2014年よりGMOペパボ株式会社でホスティング事業や

    技術部で主にプラットフォームエンジニアリングに従事。 2025年よりGO株式会社においてバックエンド開発。 趣味は旅行、キャンプ、ハードワーク
  2. © 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都道府県に拡 大
  3. © 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万ダウンロード突破!
  4. © GO Inc. このプロジェクト (Go) 一般的なCAの呼び名 責務 handler/ Controller層 HTTPリクエスト処理

    controller/ UseCase層 ビジネスロジックの中核 usecase/ Interface Adapter層 DB操作の橋渡し domain/model Entity層 ドメインモデル domain/service Domain Service層 複数controller間の調整 infra/ Infrastructure層 Repository実装 独自の命名規則と責務
  5. © 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に存在
  6. © 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 { ... } …が永遠に続く 人は同じところにコードを書き足しがち 元々は一つしかなかった バリデーションも足して 書かれてく
  7. © GO Inc. domain/service を取り入れた 標準的なフロー コードベースの大半は、単一のビジネスロジックを 実行するシンプルなフローを採用 複雑なフロー (domain/service

    経由) 複数の controller を組み合わせる必要がある複 雑なロジックに対しては、調整役として domain/service を追加 handler → controller → usecase → infra handler → domain/service → controller → usecase → infra
  8. © 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でシュッとまとめる
  9. © GO Inc. 第一の整備:バリデーションの散在をリファクタリングする domain/serviceは便利だが 1. 使いすぎると、Fatなdomain/serviceが増え、 逆に見通しが悪くなるのでポリシーを書いた 2. 既存のアーキテクチャの課題や処理の重複などは

    入社直後が一番見えるので、同質化する前 に 直すのが大事 3. 成長サービスはリリース優先とか言いがち だが、 なんと、成長しなくてもリリースは優先される ので リファクタリングは今やらないと一生やらない
  10. © GO Inc. ユーザー認証区分の追加 ナイーブに実装するとこうなる // あちこちのhandlerやcontrollerに生えていく if profile.Name ==

    "examle" { limit = 3 } if user.IsExample && !card.Is3DSVerified { return errors.New("3DS認証が必要です") }
  11. © GO Inc. 新機能とナイーブな実装の限界 ユーザー認証区分追加の設計時に直面した課題 リスク制御のための制約 新しいユーザー認証区分では 「クレカ3DS必須」「1⽇3件上 限」「エリア限定」「AI予約禁 ⽌」など、機能単位の厳しい制約

    を設ける必要があった if文の散在問題 これらの制約を素朴に実装する と、あちこちのhandlerにif user.Exampleという分岐が⽣え、 第⼀の整備と同じ問題が再発して しまいます。 将来の負債化 別の区分追加や法⼈向けプランな ど、新しいユーザー種別が追加さ れるたびに同じパターンが繰り返 され、仕様の全容把握が不可能に なります。
  12. © GO Inc. 機能制約 UserCapabilityインターフェース type UserCapability interface { DailyDispatchLimit()

    *int IsEndpointGroupAllowed(string) bool Is3DSRequired() bool CanUseFamilyLink() bool // ... } 「このユーザーは何ができるか」をインターフェースとして定義することで、 ユーザー種別の仕様をコードとして表現した。 通常ユーザーは DefaultUserCapability 制限ユーザーは LimitedUserCapability 新規ユーザー種別追加時も、既存コードの 変更は不要
  13. © GO Inc. ユーザー認証区分の追加 インターフェース化することで変更に強くなった // capability profilesを取得して3DS必須かチェック profiles, err

    := userCapabilityController.GetActiveProfilesByUserID(userID) if err != nil { return policy, err } if profiles.Is3DSRequired() { // something }
  14. © GO Inc. 機能単位のアクセス制御 機能の詳細はインタフェースで処理して、機能グループはエンドポイント制御する 機能単位のグルーピング 200本以上あるAPIエンドポイントを「配⾞」「AI予 約」「貸切」などの機能単位に分類‧グループ化 分類作業にはAIツールを活⽤し、草案を短時間で 作成することで効率的に作業を進めた

    リクエスト時の自動判定 実⾏時にリクエストの「メソッド」と「パス」から グループ名を動的に解決する仕組みを構築 Capabilityの IsEndpointGroupAllowed に渡し、 エンドポイントが増えてもCapability側の変更が 不要な設計を実現
  15. © 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) } }
  16. © GO Inc. 第三の整備: DBスキーマ変更の課題 成長に伴うデータ量増加により、手作業での運用が限界を迎えていた テーブルロックの回避 MySQLの⼤規模テーブルに対する 直接的なALTER TABLEはロックを

    引き起こすため、外部ツール (pt-online-schema-change)の 使⽤が不可⽋ 属人化した判断 「通常のALTERでいくか、pt-osc を使うか」の判断基準や実⾏⼿順 が、⼀部のエンジニアの暗黙知に 依存 深夜作業とtoil 変更のたびに⼿順書をコピーし、 担当者2名が深夜2時に集まって⼿ 作業で実⾏するという、典型的な toil(労苦)が常態化
  17. © 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使用率を常時監視...
  18. © GO Inc. 31 運用課題を解決するalterguard作った 1. ALTER文を食わせると、対象のテーブルの行数に 応じてALTER実行するか、pt-oscするか自動で決定して実行 2. DryRunやSlack通知を備えており、自動化に特化

    3. テーブルのリネームやクリーンアップも対応 a. リネーム時に新旧のテーブル行数を精査 b. 成功時のクリーンアップだけじゃなく、失敗時の クリーンアップも対応 c. スイッチする時はバッファプールから古いテーブルが消えてることを 確認してスイッチする https://github.com/pyama86/alterguard/
  19. 判断と手順のコード化 • ⾃動実⾏: PRマージ後、CI (GitHub Actions) 経由で対象 テーブルの⾏数を取得し、閾値に応じて最適な⼿法を⾃動 選択。 •

    統合と通知: Kubernetes Jobとして実⾏され、完了後は Slackへ⾃動通知。 • toilの削減: 完全に無⼈化はできないものの、変更のたびに 深夜に集まる⼿作業の⼤部分を排除することに成功 第三の整備: alterguardによる自動化
  20. 散らばるものを集める 複数箇所に重複していたバリデー ションを1箇所に集約。 「ここを読めば仕様がわかる」と いう明確な居場所を作ることで、 認知負荷と変更コストを下げる 安全な抽象を作る UserCapabilityインターフェース の導⼊。 仕様の条件をコードとして抽象化

    することで、既存コードを壊さず に次の変更を安全に乗せられる 構造を作る 判断をコードに落とす DB変更ツールalterguardの開発。 ⼈間の暗黙知や繰り返される⼿作 業(toil)を⾃動化し、エンジニア が本来の開発に注⼒できる環境を 整備する 今日話したこと