AI時代のソフトウェアプロダクトのアーキテクチャを考える連載企画の第1回です。AIによってプロトタイピングをいかに早くするか(したか)という話には注目があつまりやすいのですが、中期的にメンテナンスできるアーキテクチャのほうも重要であると考えているのでこのような記事を書いてみることにしました。連載といいましたが1回で終わったらごめんなさい。

今回はリポジトリ管理です。結論からいうとモノレポを推します。

モノレポとは

モノレポが初見のかたむけに説明をすると、単純な例で言うと、複数のアプリのコードを単一(モノ)のリポジトリで管理するリポジトリ管理のプラクティスやアプローチのひとつです。


複数のアプリと書きましたが、実際以下のいずれのケースでもモノレポと言えると思われます。

  • フロントエンドとバックエンドのアプリを管理するパターン
  • サービスAとサービスBのフルスタックアプリを管理するパターン
  • アプリ固有のコードと、共有ライブラリのコードを管理するパターン

ちなみにおそらくマイクロサービスにおけるリポジトリ管理のプラクティスのひとつということにもなっていると思うのですが、この掛け合わせの実戦経験がないので言及のスコープからは外します。

ひとつだけ意見があるとすると、モノレポのメリットを最大限活かすには技術スタックやツールをある程度限定しておいたほうがよく(後述)、だとするとマイクロサービスのやりたいことと衝突するのではないかと思われ、少なくともここで解説するモノレポはマイクロサービスにはあまり適用ができないのではないかと考えています。

モノレポとは、という定義から入ったのですが、あまり難しいことを考える必要はなくて、Turborepo を使います。

Turborepo とは

Turborepoは、TypeScriptのプロジェクトでモノレポ構成をとる場合のデファクトスタンダードとなっているツールです。いちおうモノレポに限定しないビルドツールということになっていますが、基本的にはモノレポを採用したいときに使います(あとはビルドキャッシュを上手いこと扱いたいという場合などに使われそう)。

Turborepo
Turborepo is a build system optimized for JavaScript and TypeScript, written in Rust.

Turborepoを使ってWebフロントエンドとバックエンドアプリを開発する場合のディレクトリ例を以下に示します。

my-turborepo/
├── apps/
│   ├── web/                  # Next.js などのフロントエンドアプリ
│   │   ├── src/
│   │   ├── package.json
│   │   └── tsconfig.json
│   └── api/                  # バックエンドアプリ(Hono, Express など)
│       ├── src/
│       ├── package.json
│       └── tsconfig.json
├── packages/
│   ├── ui/                   # 共有UIコンポーネントライブラリ
│   │   ├── src/
│   │   ├── package.json
│   │   └── tsconfig.json
│   ├── tsconfig/             # 共有 TypeScript 設定
│   │   ├── base.json
│   │   ├── nextjs.json
│   │   └── package.json
│   └── utils/                # 共有ユーティリティ
│       ├── src/
│       ├── package.json
│       └── tsconfig.json
├── .claude/
│   ├── CLAUDE.md
│   └── skills/
├── .codex/
│   └── skills/
├── turbo.json
├── biome.json
├── package.json
├── pnpm-workspace.yaml
└── tsconfig.json

これをみると分かりやすいのですが、appsディレクトリで複数のアプリを管理し、共通のライブラリとして使うコードやユーティリティのコードはpackagesに切り出します。tsconfigなどの設定も共通して配置したい場合packagesに配置します。

このディレクトリ構成を管理するだけだとTurborepoがあってもなくても実現はできるのですが、Turborepoは各appsやpackages配下のpackages.jsonの中身を読みに行って、ビルドやテストを一括で実行したり、並列で実行したりといったことをやってくれます。あとは前述のキャッシュ管理をよしなにやってくれる機能が備わっているので、ビルドに時間がかかるプロジェクトの場合有利になる場合があります。

モノレポにすると何が嬉しいか

AI時代のプロダクト開発として、ナレッジベースマネジメントを行っていくことが肝要と言われています。このナレッジベースには、当然コードベースも含まれています。ナレッジベースは蓄積されていても、参照されなければ意味がありません。参照をし易くするには、できるだけ近くにナレッジを集積しておく必要があります。そして参照がし易いかどうかはAIを基準に考える必要があります。

モノレポにしておくと、GitHubの単一のリポジトリをcloneした瞬間から、プロダクトに必要なナレッジ——アプリのコードや開発ドキュメント、エージェントスキルといったもの——が揃った状態になります。複数のリポジトリを同一プロジェクトだとAIに認識させるための方法もあるにはあると思いますが、そのような工夫をせずに、たんにリポジトリのなかにすべてがあるという状態をつくることができます。

モノレポに対する反対意見として、フロントエンドとバックエンドはAPIの仕様をもとに通信をすればいいのでコンテクストのミックスは不要であるという考えがあるかも知れません。個人的には、AIコーディング時代においてはこの考え方はアップデートしてよく、とにかく全体像を把握しながら最速で開発をしたり問題を発見できる構成のほうがレバレッジが効くと考えます。

モノレポがよりうまくいく条件

モノレポはAI時代と相性が良いという主旨のことを述べました。しかしいくつか条件があると考えます。

1. バックエンドのコードがTypeScriptで書かれていること

モノレポについてだけいうと、フロントエンドがTypeScriptでバックエンドがPythonであっても成立はします。が、その場合utilsなどはフロントエンド専用の、ということになるため全体の共有資産として活用する幅が小さくなります。LinterやFormatterなどのプラクティスの管理・運用が複数のプログラミング言語分になるというデメリットもあります。

Turborepoの仕様を最初の論点にしないためここに補足として記述しますが、TurborepoはTypeScript(というよりpackage.json)をメインのターゲットにしているためその点でもTypeScriptに揃えておく必要があります。

2. レビュープロセスがAIコーディングにあうものになっていること

モノレポになっていると、単一機能の実装を完遂する場合、フロントエンドのコードとバックエンドのコードを一気通貫で(AIが)実装します。そのため、Pull-Request(以下、PR)を出すとするとPRの単位が一気通貫に実装されたコードとなりがちです。もちろん実装を分離すればいいし、PRも分離すればいいのですが、もはやこれまでの開発プロセスのプラクティスに合わせていたのでは人間がボトルネックになってしまうので、最低でもAIレビューの導入など、あらたなプラクティスが導入されている必要があります。

実例 - クロノックの場合

弊アプリ・クロノックの場合、リポジトリはモノレポで、バックエンドも含めてTypeScriptで開発、ディレクトリは以下の構成になっています。

chronock/
├── apps/
│   ├── chronock-app/              # フロントエンド(React + TanStack Start)
│   ├── chronock-backend/          # バックエンド(Bun + Connect RPC)
│   ├── chronock-book/             # 予約ページ(React + TanStack Start)
│   └── chronock-www/              # マーケティングサイト(Astro)
│
├── packages/
│   ├── contract/                  # Proto 定義 & 生成コード
│   ├── i18n/                      # 国際化ユーティリティ
│   └── typescript-config/         # 共通 TypeScript 設定
│
├── specs/                         # 仕様書
│
├── .claude/
│   ├── CLAUDE.md
│   └── skills/
├── .codex/
│   └── skills/
├── biome.jsonc                    # Linter / Formatter 設定
├── bunfig.toml                    # Bun 設定
├── docker-compose.local.yml
├── package.json
└── turbo.json                     # Turborepo 設定

アプリではないランディングページとしてのWebサイトはAstroで実装しており、Astroのコードも含めてモノレポ内にあります。(まだそんなに実行できていないのですが)アプリ内の機能からマーケティングメッセージをつくってWebサイトに反映して、といったタスクや、最新の実装状況とWebページとで矛盾する訴求がないか調べるといったタスクをAIに依頼し易くなるのではないかと思います。

参考)

複数カレンダー同期 & 日程調整ツール | クロノック
複数のGoogleカレンダーをリアルタイム同期。空き時間を自動判定し、日程調整リンクで予約を受付。予約管理・CRM機能も搭載したオールインワンの日程調整ツール。

フロントエンドとバックエンドの通信はConnect RPCで行います。ConnectはProtocol Buffers(Protobuf)はデータ送受信のインターフェースを.protoファイルに記述するのですが、その定義ファイルをpackages/contractに配置しています。こうしておくことで、定義ファイルの管理とフロントエンドのコード生成とバックエンドのコード生成それぞれをスムーズに管理できます。
また、プロダクトの仕様書やエージェントスキルを同一リポジトリにまとめることで、コーディングエージェントが動きやすくなることは想像に難くないでしょう。

今回のまとめ

AI時代のアーキテクチャとして...

  • モノレポでコンテキストを集積してナレッジベースとして育てていくことが大事
  • 技術スタックをTypeScriptに揃えておくと何かとよいことが多い
  • Pull-requestの課題は解決しないので独立した解決策が必要

以上です。