ストーリー
DDDとは
ドメイン駆動設計(Domain-Driven Design)は、Eric Evansが2003年に提唱した、複雑なビジネスドメインをソフトウェアに正確に反映するための設計手法です。
DDD
├── 戦略的設計(Strategic Design)
│ ├── Bounded Context(境界づけられたコンテキスト)
│ ├── Ubiquitous Language(ユビキタス言語)
│ └── Context Map(コンテキストマップ)
└── 戦術的設計(Tactical Design)
├── Entity
├── Value Object
├── Aggregate
├── Domain Event
├── Repository
└── Domain Service
Ubiquitous Language(ユビキタス言語)
ユビキタス言語とは、開発者とドメインエキスパート(業務担当者)が共通で使う言葉です。
なぜ必要なのか
ドメインエキスパート: 「この注文を"出荷停止"にして」
開発者: 「statusをCANCELLEDに更新ですね」
ドメインエキスパート: 「いや、出荷停止とキャンセルは違う。
出荷停止は一時的に止めるだけ。
キャンセルは注文そのものを取り消す」
→ 言葉のズレが、コードのバグになる
コードに反映する
// NG: 開発者の言葉(ドメインの概念が失われている)
class Order {
updateStatus(status: string): void {
this.status = status;
}
}
// OK: ユビキタス言語(ドメインエキスパートの言葉がそのままコード)
class Order {
// 「出荷を停止する」
holdShipment(): void {
if (this._status !== OrderStatus.CONFIRMED) {
throw new Error('確認済みの注文のみ出荷停止できます');
}
this._status = OrderStatus.HELD;
}
// 「注文をキャンセルする」
cancel(): void {
if (this._status === OrderStatus.SHIPPED) {
throw new Error('出荷済みの注文はキャンセルできません');
}
this._status = OrderStatus.CANCELLED;
}
// 「出荷を再開する」
resumeShipment(): void {
if (this._status !== OrderStatus.HELD) {
throw new Error('出荷停止中の注文のみ再開できます');
}
this._status = OrderStatus.CONFIRMED;
}
}
Bounded Context(境界づけられたコンテキスト)
Bounded Contextとは、特定のドメインモデルが有効な境界です。同じ言葉でも、コンテキストによって意味が異なります。
例: ECサイトの「商品」
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ カタログBC │ │ 注文BC │ │ 在庫BC │
│ │ │ │ │ │
│ Product │ │ OrderItem │ │ StockItem │
│ - name │ │ - productId │ │ - productId │
│ - description │ │ - quantity │ │ - quantity │
│ - price │ │ - unitPrice │ │ - location │
│ - images │ │ - subtotal │ │ - reorderLevel │
│ - category │ │ │ │ │
│ │ │ ※価格は注文時に │ │ ※物理的な在庫 │
│ ※表示用の情報 │ │ 確定した金額 │ │ 管理の情報 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
同じ「商品」でもコンテキストごとに異なるモデルを持つ
// カタログBC: 表示用の情報に焦点
// catalog/domain/Product.ts
class Product {
constructor(
readonly id: ProductId,
private _name: string,
private _description: string,
private _price: Money,
private _images: ProductImage[],
private _category: Category
) {}
updatePrice(newPrice: Money): void { /* ... */ }
addImage(image: ProductImage): void { /* ... */ }
}
// 注文BC: 注文時の金額に焦点
// ordering/domain/OrderItem.ts
class OrderItem {
constructor(
readonly productId: string, // カタログBCのProductIdを参照
private _quantity: number,
private _unitPrice: Money // 注文時に確定した金額
) {}
get subtotal(): Money {
return this._unitPrice.multiply(this._quantity);
}
}
// 在庫BC: 物理的な在庫管理に焦点
// inventory/domain/StockItem.ts
class StockItem {
constructor(
readonly productId: string,
private _quantity: number,
private _location: WarehouseLocation,
private _reorderLevel: number
) {}
needsReorder(): boolean {
return this._quantity <= this._reorderLevel;
}
}
Bounded Contextの特定方法
ステップ1: ドメインエキスパートとの対話
「注文管理について教えてください」
→ 注文の作成、確認、出荷停止、キャンセル、返品
「在庫管理はどう違いますか?」
→ 入荷、出荷引当、棚卸し、ロケーション管理
「カタログの商品情報と在庫の商品情報は同じですか?」
→ いいえ。カタログは顧客向けの情報、在庫は倉庫の物理的な情報です
→ 3つのBounded Context: カタログ、注文、在庫
ステップ2: 言語の境界を見つける
同じ言葉が異なる意味を持つ箇所が、Bounded Contextの境界です。
| 言葉 | カタログBC | 注文BC | 在庫BC |
|---|---|---|---|
| 商品 | 表示情報 | 注文明細 | 在庫品目 |
| 価格 | 定価 | 注文時確定価格 | 仕入原価 |
| 数量 | なし | 注文数 | 在庫数 |
Bounded Context間の連携
異なるBounded Context間では、ドメインモデルを直接共有しません。IDで参照するか、イベントで連携します。
// 注文BCから在庫BCへの連携
// NG: 直接参照(境界を越えてモデルを共有)
class CreateOrderUseCase {
constructor(private stockItemRepo: StockItemRepository) {} // 在庫BCのリポジトリを直接使用
}
// OK: ドメインイベントで疎結合に連携
class CreateOrderUseCase {
constructor(
private orderRepo: OrderRepository,
private eventPublisher: DomainEventPublisher
) {}
async execute(input: CreateOrderInput): Promise<CreateOrderOutput> {
const order = Order.create(input.customerId, input.items);
await this.orderRepo.save(order);
// イベントを発行して在庫BCに通知
await this.eventPublisher.publish(
new OrderCreatedEvent(order.id, order.items)
);
return { orderId: order.id.value };
}
}
まとめ
| ポイント | 内容 |
|---|---|
| DDD | 複雑なドメインをソフトウェアに正確に反映する設計手法 |
| ユビキタス言語 | 開発者とドメインエキスパートの共通言語 |
| Bounded Context | 特定のドメインモデルが有効な境界 |
| 連携方法 | IDで参照、またはドメインイベントで疎結合に |
| 言語の境界 | 同じ言葉が異なる意味を持つ箇所がBCの境界 |
チェックリスト
- ユビキタス言語の重要性を説明できる
- Bounded Contextの概念を説明できる
- 同じ「商品」がコンテキストごとに異なるモデルを持つことを理解した
- Bounded Context間の連携方法(ID参照、イベント)を理解した
次のステップへ
次は「DDDの戦術的設計」を学びます。Entity、Value Object、Aggregateなど、コードレベルのDDDパターンを身につけましょう。
推定読了時間: 40分