新規プロダクトの設計会議で「最初からマイクロサービスにしておきましょう、あとで困るので」という提案を聞いたら、一度立ち止まる価値があります。
マイクロサービスが解決する問題は実在します。しかしその代金は高額で、しかも前払いです。この記事では、多くのチームにとっての現実解であるモジュラモノリス(Modular Monolith)——単一のデプロイ単位の中に厳密なモジュール境界を作るアーキテクチャ——を、具体的な設計ルールまで落として解説します。
マイクロサービスの「隠れ請求書」
サービスを分割した瞬間、関数呼び出しだったものがネットワーク越しの通信になります。これだけで、次の請求書が届きます。
- 部分失敗: 相手サービスが落ちている・遅いという状態が常に存在する。タイムアウト、リトライ、サーキットブレーカーの設計が全呼び出しに必要
- 分散データ整合性: DBが分かれるとトランザクションで守れない。結果整合性・サーガパターン・補償処理という難易度の高い道具が必要になる
- 観測性: 1リクエストが複数サービスをまたぐため、分散トレーシングがないと障害調査ができない
- 基盤コスト: サービスごとのCI/CD、デプロイ、環境構築、バージョン互換性の管理
- 開発体験: ローカルで全体を動かすのが難しくなり、変更が複数リポジトリ・複数デプロイにまたがる
数百人の開発組織なら、チームの独立性がこのコストを上回ります。しかし5〜20人のチームでは、分散システムの税金を払った結果、機能開発が遅くなるのが典型的な失敗パターンです。
モノリスの本当の問題を分解する
「モノリスがつらい」と言うとき、実際には2つの別の問題が混ざっています。
- 境界の崩壊 — どこからでも何でも呼べるため、依存が絡まり、変更の影響範囲が読めなくなる(いわゆる泥団子)
- 単位の制約 — デプロイ・スケール・障害隔離の単位が1つしかない
マイクロサービスはこの2つを同時に解決しますが、多くのチームが本当に苦しんでいるのは(1)だけです。そして、境界の崩壊はプロセスを分割しなくても解決できます。これがモジュラモノリスの出発点です。
モジュラモノリスの設計ルール
「モジュールに分けたつもりのモノリス」と「モジュラモノリス」を分けるのは、境界の強制力です。ルールは4つあります。
ルール1: モジュールは公開インターフェースだけを晒す
各モジュール(例: orders、billing、inventory)は、外部に公開するAPI(インターフェースと型)を明示的に定義し、内部実装への直接アクセスを禁止します。言語機能で表現するなら、Javaのpackage-private、Goの internal パッケージ、TypeScriptなら「モジュールのルート index.ts からエクスポートしたものだけが公開」といった規約です。
ルール2: DBはモジュールごとに分離する
同じDBインスタンスでよいので、スキーマ(またはテーブル群)をモジュールごとに分け、モジュールをまたぐJOINと外部キーを禁止します。他モジュールのデータが欲しければ、そのモジュールの公開APIを呼ぶこと。
これが一番破られやすいルールです。JOINを1本書けば済むところをAPI経由にするのは、短期的には非効率に見えます。しかしモジュール間がデータレベルで癒着した瞬間、境界は事実上消滅します。
ルール3: モジュール間の連携手段を限定する
モジュール間のやりとりは、(a)公開インターフェースの同期呼び出し、(b)ドメインイベントの発行・購読、の2種類に限定し、循環依存を禁止します。イベント駆動にしておいた連携は、将来サービスを切り出すときにそのままメッセージング基盤に置き換えられます。
ルール4: 境界をCIで強制する
規約は破られます。人間のレビューではなく、アーキテクチャテストで機械的に守ります。
- Java/Kotlin: ArchUnit で「
orders.internalに他モジュールから依存してはならない」をテストとして書く - Python: import-linter で層とモジュールの依存方向を宣言する
- TypeScript: eslint の境界系プラグイン(boundaries など)で import 制約をかける
- Go:
internalディレクトリ構成そのものがコンパイラで強制してくれる
「境界違反でCIが赤くなる」ようになって初めて、モジュラモノリスは維持可能になります。
いつ本当に切り出すのか
モジュラモノリスは「マイクロサービスをやらない宣言」ではなく、切り出しを遅延させ、正しい境界を学んでから分割する戦略です。次のシグナルが出たモジュールから切り出します。
- スケール特性が明確に違う(画像処理だけGPUが要る、特定処理だけ負荷が桁違い)
- チーム境界と一致した(そのモジュールを専任チームが持ち、独立リリースしたい——コンウェイの法則)
- 技術要件が違う(この部分だけ別言語・別ランタイムが最適)
- 障害隔離の要求(この機能だけは他の障害から守りたい/他を巻き込みたくない)
逆に「なんとなく将来のため」は切り出しの理由になりません。境界がイベントとインターフェース越しに整理されていれば、モジュール→サービスへの昇格はかなり機械的な作業になります。切り出しやすさを保つことと、切り出すことは別です。
判断チェックリスト
新規プロダクトでアーキテクチャを決めるときの目安です。
- 開発チームは20人未満か → Yesならモジュラモノリスから
- ドメイン境界にまだ自信がないか → Yesならなおさらモノリスから(間違った境界のマイクロサービスは、間違った境界のモノリスより直しにくい)
- 特定機能に極端なスケール・隔離要件があるか → その機能だけ最初から分ける「モノリス+衛星」構成も可
- 組織がすでに複数の独立チームで、リリースの衝突が起きているか → マイクロサービスの価値が出る局面
まとめ
- マイクロサービスの分散コストは前払い。小さいチームでは機能開発を遅くしがち
- モノリスの本当の問題は「境界の崩壊」であり、それはプロセス分割なしで解決できる
- モジュラモノリスの要点は、公開インターフェース限定・DB分離・連携手段の限定・CIによる境界の強制の4つ
- 切り出しは、スケール・チーム・技術・障害隔離のシグナルが出てから。境界を学んでから分割する
アーキテクチャの議論は「モノリスvsマイクロサービス」の二択で語られがちですが、実務の主戦場はその間にあります。まず境界を作り、境界を守る仕組みを作る——地味ですが、これが一番速い道です。