本文へ移動
Monorepo · One repository

モノレポと開発環境Monorepo

松葉は、画面・業務 API・Drive 同期・ウイルス検査という性格の異なる 4 つのアプリを、1 つのリポジトリ(モノレポ。複数のアプリ・部品をまとめて 1 か所で管理する方式)に同居させています。しかも言語が TypeScript と Go の 2 種類にまたがります。これをばらばらに散らさず、整合の取れた状態で扱うために 4 つの仕組み(軸)を組み合わせました。あわせて、手元のパソコンで全サービスを起動できる開発環境も整えています。

実装済み: 4 軸のワークスペースとローカル開発環境 今後: 各パッケージ・アプリの中身を実装
01

1 リポジトリを束ねる 4 つの軸Four axes

「複数のアプリを 1 か所で管理する」と言葉にすると簡単ですが、言語もビルド方法も異なるものを矛盾なく束ねるには、役割の違う道具を組み合わせる必要があります。松葉では次の 4 つを使い分けています。

図 01 · 4 軸が、それぞれ別の対象を束ねる
matsuba リポジトリ(1 つ)apps/ 4 アプリ + packages/ 7 共通部品AXIS 1pnpm workspaceTypeScript のパッケージ 9 個をまとめて依存解決web・api・packages 7AXIS 2Turborepoビルド・検査などの作業を並列実行&結果をキャッシュ13 タスクを定義AXIS 3go.workGo のモジュールを束ねる(同期・検査+生成した Go 型)sync・scanner・eventsAXIS 4Bufprotobuf の定義をTypeScript と Go に自動生成packages/events4 軸は重複せず、それぞれ「TypeScript の依存」「作業の実行」「Go の依存」「型の生成」という別の責任を持ちます。1 リポジトリ = 1 つの正しい状態。バージョン不整合や「片方だけ古い」を起こさない。

なぜ 1 リポジトリにまとめるのか: 4 つのアプリと 7 つの共通部品は、互いに型やルールを共有します。別々のリポジトリに分けると「片方だけ更新して食い違う」事故が起きがちです。1 か所にまとめ、上の 4 軸で束ねることで、常に全体が 1 つの整合した状態に保たれます。

軸(道具)束ねる対象業務上の意味
pnpm workspaceTypeScript パッケージ 9 個共通部品の使い回しと、依存関係の一括解決(同じものを各所で重複インストールしない)
Turborepoビルド・検査などの作業(13 タスク)変更のあった部分だけを並列で実行し、前回と同じ結果はキャッシュから再利用して待ち時間を短縮
go.workGo モジュール(sync / scanner + 生成した Go 型)Go 側のアプリと、自動生成した型を、1 つの作業空間として参照
Bufprotobuf 定義(packages/events)イベントの形を 1 か所で定義し、TypeScript と Go の両方へ型を生成(詳細は「型と契約」へ)
02

同居する 4 アプリと 7 共通部品Apps & packages

リポジトリの中身は、大きく アプリ(apps/)と共通部品(packages/)に分かれます。アプリ名は @matsuba/*、共通部品は @repo/* という接頭辞で区別しています。名前と業務上の役割の対応は次のとおりです。

名前種別役割(業務上の意味)
@matsuba/webアプリ(Next.js)利用者が触る画面(ブラウザ側)
@matsuba/apiアプリ(Hono)業務処理の本体(API)+ 定期バッチ(同じ部品を使い回し)
@matsuba/syncアプリ(Go)Google ドライブへの同期
@matsuba/scannerアプリ(Go)アップロードされたファイルのウイルス検査
@repo/database共通部品テーブル定義と、安全に DB へ触る 3 経路を集約
@repo/shared共通部品画面とサーバが共有する検証ルール(Zod スキーマ)・定数・型の唯一の置き場
@repo/auth共通部品認証・権限(誰がログインでき、どの Box で何ができるか)
@repo/events共通部品システム内の「出来事」の形(protobuf)。TypeScript と Go を自動生成
@repo/ui共通部品画面の共通部品の土台
@repo/eslint-config共通部品コード品質ルールの共通化
@repo/typescript-config共通部品TypeScript 設定の共通化

名前の付け方にも意味があります: アプリ(@matsuba/*)はそのまま本番の 4 つの Cloud Run サービスに 1 対 1 で対応します(@matsuba/api だけは API サービスと定期バッチの 2 つを兼ねます)。共通部品(@repo/*)は単体では動かず、アプリから呼び出されて初めて働く「部品」です。

03

手元のパソコンで全部を動かすLocal development

開発者が手元で動作を確認できるよう、4 サービスをローカルで起動できる環境を整えました。性格に応じて起動方法を分けています。画面と API はそのままパソコン上で、Go の 2 つはコンテナ(Docker)で起動します。

実線 = 整備済み・確認済み(本番リソースへ直結) 破線 = 本番デプロイ後に確認する経路
図 02 · ローカル 4 サービス → 本番リソースへ直結
ローカルPC(ホスト起動)weblocalhost:3000pnpm devapilocalhost:8080tsx watchDocker Compose(コンテナ起動)synclocalhost:8081Goscannerlocalhost:8082Go(検査は今後)本番リソース(直結)Supabase PostgresデータベースCloud Storageファイル置き場Google Drive同期先Resendメール送信メッセージ基盤を経由する処理は本番で確認Pub/Sub の配信 · Eventarc の起動 · Cloud Tasks · Scheduler はクラウド側の仕組みのため、本番デプロイ後に本番で疎通確認する

本番に直接つなぐ理由: 現在は公開前のため、本番環境そのものをテスト環境として使っています。手元の 4 サービスは、本番のデータベース・ストレージ・Drive・メール基盤に直接つないで動作確認します。一方、Pub/Sub(メッセージ基盤)や Eventarc(イベント連携)を介する処理はクラウド側の仕組みなので、これらは本番にデプロイしてから本番側で確かめます。

このアプリでの例開発者が起動コマンドを 2 つ打つと、画面(web)と業務 API(api)がパソコン上で、Drive 同期(sync)とウイルス検査(scanner)がコンテナで立ち上がり、4 つすべてが本番のデータベースに繋がって /health(生存確認)に応答します。
必要なツールバージョン用途
Node.js22画面・API をホストで起動
pnpm9 以上TypeScript パッケージの管理・起動
Go1.25(必須要件は 1.23 以上)同期・検査アプリのビルド・補完(コンテナ起動のため任意)
gcloud CLI + ADC—本番 Google Cloud リソースへの認証
buf CLI—protobuf からの型生成
Docker Desktop—同期・検査アプリのコンテナ起動

正直な注記: この開発環境を整えた段階では、各アプリの中身はまだ「起動雛形(空の骨格)」です。/health に応答することと、本番リソースに繋がることを先に確認し、業務処理は今後の開発で、この土台の上に実装します。なお、本番データベースへの構造変更(マイグレーション)は事故防止のため自動実行せず、人手で慎重に適用する運用にしています。