ストーリー
高
高橋アーキテクト
“マイクロサービスにしたい”という声をよく聞く
高
高橋アーキテクト
でも、マイクロサービスはメリットだけでなく、分散システムの複雑さというコストがある。チーム規模が小さいなら、まずはモジュラーモノリスを検討すべきだ
高
高橋アーキテクト
モジュラーモノリス…モノリスなのにモジュラー?
高
高橋アーキテクト
1つのデプロイ単位だけど、内部はBounded Contextごとにモジュールとして明確に分かれている。モノリスの簡便さとマイクロサービスの境界の明確さを両立する選択だ
モノリス vs マイクロサービス vs モジュラーモノリス
従来のモノリス:
┌────────────────────────────────────┐
│ 全てが1つの塊 │
│ 注文 + 決済 + 在庫 + 配送 │
│ 依存関係は暗黙的 │
└────────────────────────────────────┘
マイクロサービス:
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│注文 │ │決済 │ │在庫 │ │配送 │
│サービス│ │サービス│ │サービス│ │サービス│
└──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘
│ HTTP/gRPC │ HTTP/gRPC │ HTTP/gRPC │
└────────────┴────────────┴────────────┘
各サービスが独立してデプロイ
モジュラーモノリス:
┌────────────────────────────────────┐
│ 1つのデプロイ単位 │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │注文 │ │決済 │ │在庫 │ │
│ │モジュール│ │モジュール│ │モジュール│ │
│ └──┬───┘ └──┬───┘ └──┬───┘ │
│ │ 公開API │ 公開API │ 公開API │
│ └─────────┴─────────┘ │
│ 内部は明確に分離、デプロイは1つ │
└────────────────────────────────────┘
モジュラーモノリスの構造
src/
├── modules/
│ ├── ordering/ # 注文モジュール(BC)
│ │ ├── domain/
│ │ │ ├── entities/
│ │ │ ├── value-objects/
│ │ │ └── ports/
│ │ ├── application/
│ │ ├── adapters/
│ │ └── index.ts # 公開API(これだけが外部に見える)
│ │
│ ├── payment/ # 決済モジュール(BC)
│ │ ├── domain/
│ │ ├── application/
│ │ ├── adapters/
│ │ └── index.ts
│ │
│ └── inventory/ # 在庫モジュール(BC)
│ ├── domain/
│ ├── application/
│ ├── adapters/
│ └── index.ts
│
├── shared/ # 共有カーネル
│ ├── domain-event.ts
│ └── money.ts
│
└── main.ts
モジュール間の境界
公開APIの定義
// modules/ordering/index.ts
// このモジュールが外部に公開するインターフェースのみをexport
// Use Cases(Driving Ports)
export { CreateOrderUseCase } from './domain/ports/in/CreateOrderUseCase';
export { CancelOrderUseCase } from './domain/ports/in/CancelOrderUseCase';
// DTOs
export { CreateOrderInput, CreateOrderOutput } from './application/dto';
// Domain Events
export { OrderCreatedEvent } from './domain/events/OrderCreatedEvent';
export { OrderCancelledEvent } from './domain/events/OrderCancelledEvent';
// 内部の実装は公開しない
// export { Order } from './domain/entities/Order'; // NG
// export { PrismaOrderRepository } from './adapters/...'; // NG
モジュール間の連携
// modules/inventory/application/OrderCreatedHandler.ts
import { OrderCreatedEvent } from '../../ordering'; // 公開APIのみ使用
class OrderCreatedHandler {
constructor(private stockService: StockReservationService) {}
async handle(event: OrderCreatedEvent): Promise<void> {
for (const item of event.items) {
await this.stockService.reserve(item.productId, item.quantity);
}
}
}
ESLintで境界を強制
// .eslintrc.js
module.exports = {
rules: {
'import/no-restricted-paths': ['error', {
zones: [
{
// 注文モジュールの内部に直接アクセスすることを禁止
target: './src/modules/inventory/',
from: './src/modules/ordering/domain/',
message: 'ordering モジュールの内部に直接アクセスできません。index.tsの公開APIを使ってください。',
},
{
target: './src/modules/ordering/',
from: './src/modules/inventory/domain/',
message: 'inventory モジュールの内部に直接アクセスできません。',
},
],
}],
},
};
比較表
| 観点 | モノリス | モジュラーモノリス | マイクロサービス |
|---|
| デプロイ | 1つ | 1つ | 独立 |
| 境界の明確さ | 低い | 高い | 高い |
| ネットワーク複雑さ | なし | なし | 高い |
| データ整合性 | 容易 | 容易 | 結果整合性 |
| チーム規模 | 小〜中 | 小〜大 | 中〜大 |
| 将来の分割 | 困難 | 容易 | 不要 |
| 運用複雑さ | 低い | 低い | 高い |
いつモジュラーモノリスを選ぶか
| 状況 | 推奨 |
|---|
| チーム5人以下 | モジュラーモノリス |
| ドメインが複雑だがチームが小さい | モジュラーモノリス |
| 将来的にマイクロサービス化を検討 | モジュラーモノリス(段階的移行) |
| チーム10人以上、独立デプロイが必要 | マイクロサービス |
| シンプルなCRUDアプリ | 従来のモノリスで十分 |
まとめ
| ポイント | 内容 |
|---|
| モジュラーモノリス | 1デプロイ単位 + 内部モジュール分離 |
| 公開API | 各モジュールはindex.tsで公開範囲を制御 |
| モジュール間連携 | ドメインイベントまたは公開APIで疎結合に |
| 境界の強制 | ESLintルールで内部アクセスを禁止 |
| 移行パス | 必要に応じてマイクロサービスに段階的移行 |
チェックリスト
次のステップへ
次は「アーキテクチャのトレードオフ」を学びます。すべてのアーキテクチャ判断にはトレードオフがあることを理解しましょう。
推定読了時間: 30分