LESSON 40分

ストーリー

佐藤CTO
モノリスの問題は見えた。次は”どこで切るか”を考えよう
佐藤CTO
サービス境界の設計を間違えると、分散モノリスという最悪の状態になる。DDDはそれを防ぐ最も有力な方法だ

なぜDDDがマイクロサービスに必要なのか

マイクロサービスの最大の課題は「どこでサービスを分割するか」です。技術的な境界(データ層、ビジネスロジック層)ではなく、ビジネスドメインの境界で分割することが成功の鍵です。

分割基準結果
技術層で分割分散モノリスフロント/API/DB各サービス
チーム構成で分割コンウェイの法則そのままチームA用/チームB用サービス
ドメインで分割疎結合サービス注文/決済/在庫サービス

戦略的DDD

サブドメインの分類

ビジネスドメインを3種類のサブドメインに分類します:

分類定義投資レベル例(EC)
Core競争優位の源泉最大(内製)レコメンデーション、価格最適化
Supportingコアを支える固有機能中(内製/カスタム)在庫管理、注文処理
Genericどのビジネスでも同じ最小(SaaS/OSS)認証、メール送信、決済

ユビキタス言語

同じ用語でもコンテキストによって意味が異なることがあります:

「商品」の意味の違い:
  カタログチーム:  商品 = 名前、説明、画像、カテゴリ
  在庫チーム:      商品 = SKU、在庫数、倉庫位置
  決済チーム:      商品 = 金額、税区分、割引適用
  配送チーム:      商品 = 重量、サイズ、配送区分

各チームが自分たちのコンテキスト内で統一された言語を使うことが重要です。


戦術的DDD

Entity(エンティティ)

同一性(ID)を持ち、ライフサイクルを通じて追跡されるオブジェクト:

class Order {
  constructor(
    readonly id: OrderId,        // 同一性
    private items: OrderItem[],
    private status: OrderStatus,
    private customerId: CustomerId
  ) {}

  addItem(item: OrderItem): void {
    if (this.status !== OrderStatus.DRAFT) {
      throw new Error('確定済み注文には追加できません');
    }
    this.items.push(item);
  }

  confirm(): void {
    if (this.items.length === 0) {
      throw new Error('空の注文は確定できません');
    }
    this.status = OrderStatus.CONFIRMED;
  }
}

Value Object(値オブジェクト)

同一性を持たず、属性の値で等価判定されるオブジェクト:

class Money {
  constructor(
    readonly amount: number,
    readonly currency: Currency
  ) {
    if (amount < 0) throw new Error('金額は0以上');
  }

  add(other: Money): Money {
    if (this.currency !== other.currency) {
      throw new Error('通貨が異なります');
    }
    return new Money(this.amount + other.amount, this.currency);
  }

  equals(other: Money): boolean {
    return this.amount === other.amount && this.currency === other.currency;
  }
}

Aggregate(集約)

トランザクション整合性の境界を定義する:

// Order集約 - OrderとOrderItemはセットでトランザクション管理
class OrderAggregate {
  private order: Order;
  private items: OrderItem[];  // Order内でのみ管理

  // 集約ルート経由でのみ操作可能
  addItem(productId: ProductId, quantity: number, price: Money): void {
    this.order.addItem(new OrderItem(productId, quantity, price));
  }

  // 他の集約への参照はIDのみ(直接参照しない)
  getCustomerId(): CustomerId {
    return this.order.customerId;  // IDのみ返す
  }
}

集約設計の原則:

  • 集約は小さく保つ
  • 他の集約はIDで参照する(直接オブジェクト参照しない)
  • 集約間の整合性は結果整合性(eventual consistency)を使う

DDDからサービス境界へ

マッピングルール

DDD概念マイクロサービス
Bounded Context1つのマイクロサービス
Aggregateサービス内のトランザクション境界
Domain Eventサービス間の非同期通信
Anti-Corruption Layerサービス間のアダプター

サービスサイズの目安

graph LR
    Small["小さすぎ\nNano Service\n関数1つ"] --- Right["適切なサイズ\n2-pizza team"]
    Right --- Big["大きすぎ\nモノリス\n全機能"]
    Small -.- Issue1["通信オーバーヘッド大"]
    Big -.- Issue2["変更影響大"]

    style Small fill:#fee2e2,stroke:#dc2626,color:#991b1b
    style Right fill:#d1fae5,stroke:#059669,stroke-width:3px,color:#065f46
    style Big fill:#fee2e2,stroke:#dc2626,color:#991b1b
    style Issue1 fill:#fef3c7,stroke:#d97706,color:#92400e
    style Issue2 fill:#fef3c7,stroke:#d97706,color:#92400e

2-pizza team ルール: 1つのサービスを1チーム(5-8名)で所有・運用できるサイズが適切。


まとめ

ポイント内容
戦略的DDDサブドメイン分類で投資優先度を決定
ユビキタス言語コンテキスト内で用語を統一する
戦術的DDDEntity/VO/Aggregate で境界を明確化
サービス境界Bounded Context = マイクロサービス

チェックリスト

  • Core/Supporting/Generic サブドメインの分類を理解した
  • ユビキタス言語の重要性を理解した
  • Entity と Value Object の違いを説明できる
  • Aggregate がトランザクション境界であることを理解した

次のステップへ

次は Bounded Context の特定とコンテキストマッピングを学びます。


推定読了時間: 40分