新規プロダクトの設計会議で「最初からマイクロサービスにしておきましょう、あとで困るので」という提案を聞いたら、一度立ち止まる価値があります。

マイクロサービスが解決する問題は実在します。しかしその代金は高額で、しかも前払いです。この記事では、多くのチームにとっての現実解であるモジュラモノリス(Modular Monolith)——単一のデプロイ単位の中に厳密なモジュール境界を作るアーキテクチャ——を、具体的な設計ルールまで落として解説します。

マイクロサービスの「隠れ請求書」

サービスを分割した瞬間、関数呼び出しだったものがネットワーク越しの通信になります。これだけで、次の請求書が届きます。

  • 部分失敗: 相手サービスが落ちている・遅いという状態が常に存在する。タイムアウト、リトライ、サーキットブレーカーの設計が全呼び出しに必要
  • 分散データ整合性: DBが分かれるとトランザクションで守れない。結果整合性・サーガパターン・補償処理という難易度の高い道具が必要になる
  • 観測性: 1リクエストが複数サービスをまたぐため、分散トレーシングがないと障害調査ができない
  • 基盤コスト: サービスごとのCI/CD、デプロイ、環境構築、バージョン互換性の管理
  • 開発体験: ローカルで全体を動かすのが難しくなり、変更が複数リポジトリ・複数デプロイにまたがる

数百人の開発組織なら、チームの独立性がこのコストを上回ります。しかし5〜20人のチームでは、分散システムの税金を払った結果、機能開発が遅くなるのが典型的な失敗パターンです。

モノリスの本当の問題を分解する

「モノリスがつらい」と言うとき、実際には2つの別の問題が混ざっています。

  1. 境界の崩壊 — どこからでも何でも呼べるため、依存が絡まり、変更の影響範囲が読めなくなる(いわゆる泥団子)
  2. 単位の制約 — デプロイ・スケール・障害隔離の単位が1つしかない

マイクロサービスはこの2つを同時に解決しますが、多くのチームが本当に苦しんでいるのは(1)だけです。そして、境界の崩壊はプロセスを分割しなくても解決できます。これがモジュラモノリスの出発点です。

モジュラモノリスの設計ルール

「モジュールに分けたつもりのモノリス」と「モジュラモノリス」を分けるのは、境界の強制力です。ルールは4つあります。

ルール1: モジュールは公開インターフェースだけを晒す

各モジュール(例: ordersbillinginventory)は、外部に公開する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マイクロサービス」の二択で語られがちですが、実務の主戦場はその間にあります。まず境界を作り、境界を守る仕組みを作る——地味ですが、これが一番速い道です。