ブログに戻る

AIでひな形を同期し続けるのをやめ、ライブラリに切り出す

同じフロントエンドをAIで複数アプリへコピーすると、レビューがボトルネックになった。約1週間で共有ライブラリを切り出し、同期し続ける構造を変えた話。

目次

しばらくの間、私たちの「プラットフォーム」はひな形のリポジトリでした。

新しい製品のフロントエンドは、同じテンプレートのコピーから始まりました。シェル、ルーティング、認証、共有ページ、状態、翻訳。すべてがテンプレートにあり、コピー先のアプリにも存在していました。共通の枠組みを変えるときは、ひな形を修正してから、同じ変更をすべてのアプリに同期するという方針でした。

AIで同期は速くなりました。レビューは速くなりませんでした。

ほぼ同じアプリの、ほぼ同じファイルにある、何千行もの似た差分を読むことになります。しかも、製品固有の変更が上書きされていないかを確認する必要があります。その運用自体がボトルネックでした。

私は約1週間で、共有コードをアプリがコピーせずインポートできる、公開パッケージからなるライブラリに切り出す作業を進めました。その1週間で基盤が完成したわけではありません。基盤をつくれる状態になったのです。それ以降は、複数の製品フロントエンドが使えるようにライブラリを育てる作業です。製品チームは製品を開発し、基盤の修正は一度で済むことを目指しています。

コード
変更前:ひな形そのものが基盤

  ひな形 → コピーとAI同期 → 製品アプリ A / B / C

  同じ共通機能に、アプリ数だけのレビューが必要

テンプレートが基盤そのものだった

ひな形は、開発を始めるには便利です。しかし、本番で動く多数のフロントエンドを運用する土台としては問題があります。

各アプリが独自のGit履歴を持つと、コピーは別のものになっていきます。ある製品に専用ログイン画面が必要になり、ナビゲーションが変わり、ポリフィルが一部のリポジトリだけに入り、接続処理が「このアプリだけ」のために分岐します。建前ではテンプレートが正でしたが、実際の正は今開いているリポジトリでした。

コストは、一度の障害より調整作業として現れました。

  • シェルや認証の修正は、一度のマージでは終わらず、複数アプリへの展開作業になる。
  • 「最新のひな形に追従しているか」に、明確な答えがない。
  • 共通部分を理解している人が、アプリごとのPRで同じ変更を繰り返しレビューする。

これは入力速度ではなく、チームの構造の問題です。AIは解決するどころか、その構造を加速させます。

「AIで同期すればいい」が問題を広げた理由

元の方針は、ひな形を正として保ち、AIで各アプリへ更新を展開して、人がレビューすることでした。

対象が小さければ機能することもあります。しかし、ルーティング、バンドラー設定、認証Cookie、クライアント状態、翻訳、遅延読み込みのウィジェットまで含むアプリ全体では、「同期」は巨大でノイズの多い差分になります。製品固有部分と基盤部分を同時に把握するのは難しく、何千行を形式的に承認するか、全製品のボトルネックになるかという状態でした。

すぐに、2つの問題が見えました。

  1. 上書きしてはいけないものを上書きする。 正当な製品固有の変更が、ひな形との差異と判断されて消される。
  2. 必要な変更が届かない。 基盤の修正がテンプレートと一部のホストには入り、分岐が進んだホストにはきれいに適用できない。

AIの生成にも、生成された似たコードのレビューにもコストを払いながら、共有ランタイムは得られませんでした。コピーが速くても、基盤にはなりません。

本当の問いは、何を一か所に持ち、何を製品ごとに持ってよいかでした。その境界がなければ、同期のたびに推測することになります。

約1週間でライブラリを切り出す

ひな形から共通の枠組みを取り出し、ホストが依存できるバージョン付きのライブラリに移しました。

最初の切り出しは、大規模な再設計ではありません。境界を引く作業です。

  • シェル、API、状態、UI、共有ページをパッケージの境界に置く。
  • 各製品アプリがバージョンを更新できる依存関係にする。
  • 「テンプレートを編集して全アプリにばらまく」ことを、基盤のリリース方法にしない。

約1週間でできたのは、ビルドでき、利用できるライブラリをリポジトリに用意することでした。全ホストの移行や、現在あるツールのすべてをつくったわけではありません。チームが必要としていたのは完璧な設計書より、次の共通修正を20個のsrc/以外に置ける場所でした。

これが転換点でした。基盤の変更を、コード同期の儀式ではなくバージョンとして届けられるようになったのです。

コード
変更後:一つのライブラリを複数ホストで利用

  共有ライブラリ(シェル・セッション・API・UI・ローダー)
     ↓ インポートとバージョン更新
  製品アプリ A / B / C
     └ 製品ページ、ナビゲーション、利用する連携機能

チームにとって何が変わったか

すぐに変わったのはレビューの負担です。

各製品リポジトリで同じ3,000行のAI同期を読む代わりに、一度のライブラリ変更と、小さなホスト側の更新をレビューできるようになります。製品固有のコードは製品のリポジトリに残り、共有すべき振る舞いを製品固有として扱う必要がなくなります。

誰が貢献できるかも変わりました。

  • 製品チームは、認証、ルーティング、外部ウィジェットの読み込みを再実装せず、製品の画面に集中できる。
  • どのホストが遅れているかを誰かが覚えていることに、基盤開発が依存しなくなる。
  • ひな形は、基盤の別コピーではなく、開始時の構造に戻せる。

共通部分をローカルで持つ、重いホストはまだ残っていました。切り出しだけで薄くなるわけではありません。ただ、シェル、起動処理、ルーティング、共通ローダーをライブラリから取り込み、ナビゲーション内容、製品ページ、有効にする連携機能をアプリ側に残す、という移行先ができました。

カスタマイズの境界は、次の製品にも同じ変更が必要なら、ホストに置くべきではないという考え方です。

コード
薄いホストの責任分担

  製品アプリ:ページ、ナビゲーション内容、機能フラグ
       ↓
  共有ライブラリ:シェル、認証、ルーティング、UI、読み込み処理
       ↓
  実行時に読み込むウィジェット(ホストのReactを共有)

必要になったアーキテクチャ

コードがパッケージに移ると、難しい問題が見えるようになりました。以前からあった問題を、テンプレートのコピーが隠していたのです。

薄いホストと重いホスト。 薄いアプリは共通部分をライブラリから取り込み、重いアプリはそれを自身のツリーに持ちます。新規アプリは薄い構造で始め、既存アプリは移行します。テンプレートは移行戦略の代わりにはなりません。

古いホストと新しいウィジェット。 既存のフェデレーション型ウィジェットを動かすため、ホストの古いReactを維持しながら、新しいUIが期待するAPIにも対応する必要がありました。Reactの共有インスタンス、必要なポリフィル、ホストがバンドルするものと実行時に提供するものの厳密な区別が必要です。新旧のUIを一つの基盤で扱うための重要な条件です。

バージョン更新と導入は別。 最新のシェルをインストールしても、ホストが実際にマウントしているか、ストアを接続したか、共有ページのローカルコピーをやめたかは分かりません。MRでのプレビュービルド、自動チェック、明示的な手順によって、「更新済み」と「接続済み」を区別するようになりました。

ひな形を第二の基盤にしない。 共通機能が増えるたびにひな形を膨らませれば、同期の世界に戻ります。テンプレートは開始方法のスナップショットで、共有の振る舞いが存在する場所はライブラリです。ひな形を二つにしても、同期がもう一つ増えるだけです。

最初の1週間で、これらがすべて解決したわけではありません。解決するための適切な場所を得たのです。

切り出した後も、各アプリに適合させ続ける

ライブラリの切り出しは問題の拡大を抑えましたが、仕事を終わらせたわけではありません。

その後も技術オーナーとして関わっています。個人のプロジェクトにするためではなく、新しい共通機能を共通の場所に置き、ホストが再び分岐する代わりにそこへ近づけるよう、チームを支えるためです。

実際の仕事には、次のものがあります。

  • 薄いホストへの移行 — ルーティング、ウィジェットのホスティング、ポリフィル、トークンの起動処理を公開パッケージへ移し、稼働中のアプリに展開する。
  • 開発ツール — CLIとエージェント用キットで、ひな形生成、検証、移行、更新を支え、再び巨大な同期差分を生まないようにする。
  • リリース運用 — バージョン管理、プライベートレジストリ、プレビュー公開、更新フローを整え、複数ホストが安全に変更を受け取れるようにする。
  • 共有の枠組みと、似て見えるページを区別する — シェル、セッション、ローダー、API、デザインシステムの基本要素はライブラリに置く。一見汎用的なページも公開しましたが、製品UIの修正に基盤の更新が必要になる待ち時間が生まれました。次の段階では、ライブラリを契約と機能に絞り、製品ページはアプリがコピーして所有できる参照実装にすることを考えています。
コード
ライブラリを小さく保つ

  公開してバージョン更新         一度コピーし、製品が所有
  セッション・API・権限・ローダー  設定・一覧・切り替えUIの参照実装
       ↓                            ↓
  各アプリがバージョンを更新       各アプリがUI修正をリリース
  • 分析と可観測性 — 共通のイベント一覧、ホストへの導入ルール、テレメトリーを整え、アプリごとに作り直さないようにする。

CLIやエージェントはアーキテクチャそのものに見えがちですが、実際にはそれを使うためのインターフェースです。共通部分が各リポジトリに残っていれば、賢い同期でも複雑さを増やします。アーキテクチャはライブラリにあり、ツールは一人が全ホストを編集しなくてもチームが使えるようにするためのものです。

今も基盤に「もう一つ機能を」と頼まれます。それが仕事です。成功の尺度は、自分が全行を書いたかではありません。次の製品がシェルの専用コピーを必要とせず、次の共通修正で各アプリのAI差分を繰り返しレビューしなくて済むかです。

テンプレートを同期し続けるチームに伝えたいこと

多数の製品フロントエンドと一つのひな形があると、AIは問題のある作業の循環を生産的に見せます。更新は生成できますが、経験のあるエンジニアの時間はレビューに使われ、全アプリの不具合を一か所で修正できる構造は得られません。

不完全でも切り出すことです。約1週間で実際に使えるライブラリをつくるほうが、分岐したコードを次の四半期も同期し続けるより前進になります。

そしてチーム内で境界を明示します。ホストが変更してよいもの、必ずインポートするもの、更新が本当に導入されたと確認する方法。その後でツールを育てます。

私たちはこうして、基盤の代わりになっていたひな形からライブラリへ進みました。ライブラリは繰り返しのレビューを減らしました。次の仕事はそれを小さく保つことです。共通の枠組みと契約はバージョンで更新できる単位にし、製品UIは製品側で所有できるようにします。