📘

モジュラーモノリス?モノリス?の設計で詰まったところの覚書

に公開

内部 API はモジュラーモノリス × クリーンアーキテクチャでは public.ts もしくは port/in、DTO など限られたファイルしか公開せず、公開されたファイルのメソッドだけを呼び出すのが API に似ていて、なおかつ、同一プロセス/システム内に限定されているから内部 API という呼び方を使っている認識です。

外部 API は HTTP 通信を介した REST API や GraphQL など通常、イメージされる API を指しています。

基本的にこの内部/外部 API を使用。
補助的な手段としてイベント駆動、MV、IMMV がある。

ただし、IMMV については現時点で AWS RDS は非対応

同一システム、同一モジュール間、同一 DB

概要: 同一モジュール内で単一の DB を使用し、明確なサブモジュールがない(単一のコードベースや密接に連携した機能群)。スキーマは分離されておらず、直接テーブル参照が主。効率化のためにマテリアライズドビュー(MV)や内部 API を補助的に活用。

スキーマ分離していないため直接該当テーブルを参照する。DB から該当テーブルを取得する際に MV 使用可能。それらを内部 API 化して同一モジュール、異なるサブモジュール間で連携可能。IMMV + API(リアルタイムの MV)の組み合わせもあり。

同一システム、同一モジュール異なるサブモジュール間同一 DB

概要: 同一モジュール内で、異なるサブモジュールが同一 DB を共有。スキーマは統一されており、データ所有権が明確なため、MV や IMMV を活用可能。

IMMV、MV 使用可能(スキーマが統一され、データ所有権が一貫しているため責務が曖昧にならないため)。

同一システム、異なるモジュール間、同一 DB

概要: 同一システム内で、異なるモジュール(例: 店舗情報モジュールとスタッフ情報モジュール)が同一 DB を共有。スキーマは論理的に分離されているため、内部 API を推奨。異なるモジュールの MV や IMMV を自モジュールが参照するのは責務の曖昧化を避けるため非推奨。ただし、イベント駆動によって他モジュールの参照テーブルを自モジュールに作成して MV を構築するのは OK。

また、双方向に呼び出しがある場合は、内部 API 使用は非推奨。
・呼び出しチェーンがループしやすく、レイテンシー爆発/デッドロックのリスク。テスト時のモック注入が複雑か
・密結合になりがち
・トランザクションがモジュールを跨いで大きくなる
双方向に呼び出す場合は非同期(イベント駆動)に移行。

スキーマが論理分離しているため内部 API 使用可能。
責務が不明確になるためパフォーマンスよくしたくてもモジュールが異なるのならば MV,IMMV(リアルタイムの MV)+ API は非推奨。
代わりにパフォーマンスは内部 API に劣ることが多いが、
インフラが高負荷の時はイベント駆動で MV 作成して連携する CQRS の読み取り最適化アプローチを検討。

同一システム、同一モジュール間、異なる DB

概要: 同一モジュール内で異なる DB を使用するケースは原則まれ(例: Read/Write 分離やキャッシュ DB 統合)。内部 API やイベント駆動でデータ同期を管理。

基本的に同一モジュールには1つの DB なので原則ありえない。(条件付きで可能(例: Read/Write 分離でメイン DB + Read Replica DB、またはキャッシュ DB 統合)。)

この場合のデータ処理の流れ:
内部 API でメイン DB 更新後、イベント/同期で Replica/キャッシュ反映。
クエリは Repository で分岐(Write: メイン、Read: Replica)。
トランザクションは Saga パターンで管理。

もしくはイベント駆動で非同期処理により負荷分散

同一システム、異なるモジュール間、異なる DB

概要: 異なるモジュールがそれぞれ独自の DB を持つ場合。クロス DB アクセスはせず、内部 API で連携。MV やビューは自 DB 限定。

クロス DB、内部 API 経由のみ、VIEW や MV 使用不可(自 DB 限定)。

異なるシステム間連携(基本的に異なるモジュール、異なる DB)

概要: 異なるシステム(例: Public VPC の店舗情報、Private VPC のスタッフ情報)間の連携。外部 API またはイベント駆動でスケーラブルに通信。MV やビューは使用不可。

外部 API 経由もしくはイベントでスケーラブルな連携。VIEW や MV 使用不可。

  • 同期: public 経由 port/in 注入。(5.1.2 )
  • 非同期: event 公開、adapter 発行/購読(フェーズ 3)。(5.2)
  • ACL(5.1.1)/投影(5.3): adapter/out 実装、port/out 抽象(フェーズ 2)。paths + dep-cruiser で public 制限(別ガイドライン 3-依存関係管理の制御、参照)。 -  MV,IMMV については別ガイドライン 4-マテリアライズドビューの使い方を参照。

基本的なモジュール間連携は以下の方法でカバー可能です。これらはシンプルで、MV より導入コストが低い。

他の連携方法で十分な場合(MV 不要の場面)

  • 内部の公開バレル経由(同期呼出): モジュラーモノリス内で、他モジュールの public/index.ts からメソッド/オブジェクトをインポートして呼ぶ。例: order モジュールが account の公開ユースケースを直接実行。リアルタイムでシンプルだが、結合が強くなりやすい(他モジュールの変更が波及)。
  • 外部 HTTP API 経由(同期/非同期呼出): マイクロサービス間や外部システム連携で、REST/gRPC API を呼ぶ。ACL(Anti-Corruption Layer)でレスポンスを自型に翻訳。ネットワーク遅延があるが、疎結合。例: 他サービス API を fetch し、結果をキャッシュ。
  • イベント購読(非同期): ドメインイベントを発行/購読。最終的一致性でスケール良い。例: account が更新イベントを発行、order が購読してローカル状態更新。リアルタイム性が必要ない場合に強い。

これらで事足りる場面: クエリがシンプル(単一テーブル参照)、データ量小、リアルタイム厳密不要、セキュリティ/パフォーマンス問題なし。

MV が必要になる場面(他の方法で不十分なケース)

MV は、他の方法でパフォーマンス低下/結合過多/セキュリティリスクが出た時に有効。主な必要場面は以下。CQRS/DDD でコマンド(更新)とクエリ(読み取り)を分離し、読み取りを最適化。イベント駆動で更新され、事前計算ビューを提供

  • 複雑クエリ/JOIN/集計でパフォーマンス悪い時:
    • 場面: 他モジュールが頻繁に複雑クエリ(複数テーブル JOIN、集計)を実行。public バレル/HTTP 呼出では毎回計算/ネットワーク負荷大、イベント購読ではクエリ自体最適化できない。
    • なぜ MV: 事前計算ビューを作成(e.g., 売上集計ビュー)。クエリが高速(キャッシュ化)。2025 年トレンド: .NET9/CQRS 統合でリアルタイム MV 進化。
    • 例: EC システムで order が account の取引履歴集計クエリ。MV で事前集計ビューを作成、order が高速参照。
  • 他モジュール/サービスからの参照が多く、生テーブル公開したくない時(セキュリティ/整合性):
    • 場面: マイクロサービス/モジュラーモノリスで、他がデータ参照多。public バレル/HTTP で生テーブル公開するとセキュリティリスク(機密漏洩)、イベント購読ではデータ構造最適化できない。
    • なぜ MV: 生テーブル非公開、投影専用テーブルで必要データだけ公開。デノーマライズ(非正規化)でクエリ簡素化。
    • 例: 共有 DB 避け、各サービスにローカル MV。account の更新イベントで order の MV 更新。
  • 最終的一致性でスケール必要な時(Event Sourcing/CQRS 連携):
    • 場面: 非同期イベント購読だけではクエリ遅い/不整合起きやすい。public/HTTP 同期では高負荷。
    • なぜ MV: Event Sourcing と組み合わせ、イベントから MV 構築/更新。リアルタイムクエリ可能。
      • 例: 銀行システムで取引イベントから残高 MV 更新。クエリがイベント蓄積に依存せず高速。

Discussion