LESSON 30分

ストーリー

高橋アーキテクト
“マイクロサービスにしたい”という声をよく聞く
高橋アーキテクト
でも、マイクロサービスはメリットだけでなく、分散システムの複雑さというコストがある。チーム規模が小さいなら、まずはモジュラーモノリスを検討すべきだ
高橋アーキテクト
モジュラーモノリス…モノリスなのにモジュラー?
高橋アーキテクト
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ルールで内部アクセスを禁止
移行パス必要に応じてマイクロサービスに段階的移行

チェックリスト

  • モジュラーモノリスの構造を説明できる
  • 公開APIでモジュール境界を定義できる
  • モノリス/モジュラーモノリス/マイクロサービスの違いを説明できる
  • チーム規模に応じた選択指針を理解した

次のステップへ

次は「アーキテクチャのトレードオフ」を学びます。すべてのアーキテクチャ判断にはトレードオフがあることを理解しましょう。


推定読了時間: 30分