DDD の基本概念 — ドメインオブジェクト・集約・リポジトリ・ユビキタス言語
ドメイン駆動設計とは
ドメイン駆動設計(Domain-Driven Design、DDD)は、ビジネスロジックを中心に据えたソフトウェア設計手法です。
DDD は「ビジネス問題」から、従来は「技術的制約」から設計を始めます。
用語定義
DDDを学ぶ上で重要な用語を定義します。
基本用語
| 用語 | 定義 | 例 |
|---|---|---|
| ドメイン | ソフトウェアが解決しようとするビジネス領域 | ECサイトにおける「注文」「在庫管理」 |
| ドメインオブジェクト | ドメインを表現するオブジェクトの総称 | エンティティと値オブジェクトの2種類 |
| ドメインモデル | ドメインオブジェクトと集約で構成された設計全体 | ドメインを表現する構造の全体像 |
| ビジネスルール | ドメインにおける制約や規則 | 「確定後は商品を追加できない」 |
ドメインオブジェクトの2種類
| 種類 | 識別子 | 同一性の判断基準 | 例 |
|---|---|---|---|
| エンティティ | あり | 識別子で判断 | Order, User, Product |
| 値オブジェクト | なし | 全属性の値で判断 | Money, Address, Email |
ドメインオブジェクトの詳細比較
値オブジェクト、エンティティ、集約の違いをより詳しく見ていきましょう。
| 観点 | 値オブジェクト | エンティティ | 集約 |
|---|---|---|---|
| 定義 | 属性の組み合わせで表現される概念 | 一意の識別子を持つ概念 | 関連するドメインオブジェクトの集まり |
| 識別子 | なし | あり(ID) | 集約ルートがIDを持つ |
| 可変性 | 不変(Immutable) | 可変(Mutable) | 集約ルート経由で変更可能 |
| 同一性判断 | すべての属性値 | 識別子(ID) | 集約ルートのID |
| ライフサイクル | 親オブジェクトに依存 | 独立して存在 | 集約ルートのライフサイクルに従う |
| テーブル表現 | 埋め込み or 値オブジェクトテーブル | 専用テーブル | 複数テーブル(集約境界内) |
具体例で理解する
// 値オブジェクト: 金額
class Money
{
private int $amount;
private string $currency;
public function __construct(int $amount, string $currency)
{
$this->amount = $amount;
$this->currency = $currency;
}
// 不変: 計算結果は新しいインスタンスを返す
public function add(Money $other): Money
{
return new Money($this->amount + $other->amount, $this->currency);
}
// 同一性: すべての属性値で判断
public function equals(Money $other): bool
{
return $this->amount === $other->amount
&& $this->currency === $other->currency;
}
}
// エンティティ: 注文
class Order
{
private OrderId $id; // 識別子
private OrderStatus $status;
private Money $total;
// 可変: 状態が変わる
public function confirm(): void
{
$this->status = OrderStatus::CONFIRMED;
}
// 同一性: IDで判断
public function equals(Order $other): bool
{
return $this->id->equals($other->id);
}
}
// 集約: Order集約(OrderとOrderLineをまとめる)
class Order // 集約ルート
{
private OrderId $id;
private array $orderLines; // 集約内のエンティティ
// 集約ルート経由でのみアクセス
public function addItem(ProductId $productId, int $quantity): void
{
$line = new OrderLine($this->id, $productId, $quantity);
$this->orderLines[] = $line;
}
}
使い分けの判断基準
判断例:
「金額」
識別子が必要? → NO
→ 値オブジェクト
「注文」
識別子が必要? → YES
他のオブジェクトと関連? → YES(注文明細と関連)
一貫性の境界? → 注文と注文明細は一緒に管理
→ Order集約の集約ルート
「注文明細」
識別子が必要? → YES
独立して存在? → NO(注文に依存)
→ Order集約内のエンティティ
集約(Aggregate)
集約はドメインオブジェクトではなく、**ドメインオブジェクトをまとめる構造(境界)**です。
集約のルール
- 外部からは集約ルート経由でのみアクセス
- 集約ルートが整合性を保証
- 1トランザクションで1集約の更新を推奨(複数集約の更新は結果整合性で対応)
// OK: 集約ルート(Order)経由でアクセス
$order = $orderRepository->findById($orderId);
$order->addItem($productId, $quantity);
// NG: OrderLineに直接アクセス
$orderLine = $orderLineRepository->findById($lineId); // これはNG
リポジトリ(Repository)
リポジトリは、集約の永続化と復元を担当するオブジェクトです。
DDDの核となる考え方
ユビキタス言語(Ubiquitous Language)
ユビキタス言語とは、チーム全体で共通して使う言葉のことです。開発者、ドメインエキスパート(業務担当者)、プロダクトマネージャーなど、プロジェクトに関わるすべての人が同じ言葉を使ってコミュニケーションを取ることで、誤解を防ぎます。
なぜユビキタス言語が重要なのか
従来のアプローチでは、業務担当者と開発者が異なる言葉を使うことで、以下の問題が発生します。
❌ ユビキタス言語がない場合
────────────────────────────
業務担当者: 「注文を確定してください」
↓(翻訳)
開発者: 「Orderテーブルのstatusカラムを1に更新します」
# 問題点:
# ・業務とコードの間に「翻訳」が必要
# ・「確定」という概念がコードから消えている
# ・要件変更時に認識のズレが生じやすい
✓ ユビキタス言語がある場合
───────────────────────────
業務担当者: 「注文を確定してください」
↓(そのまま)
開発者: 「Order.confirm()を呼び出します」
# メリット:
# ・業務とコードが同じ言葉で表現される
# ・コードを読めば業務フローが理解できる
# ・チーム全体の認識が一致する
ユビキタス言語の実践例
業務の言葉 コード
「注文を確定する」 → Order.confirm()
「注文をキャンセルする」 → Order.cancel()
「商品を追加する」 → Order.addItem()
「送料を計算する」 → Order.calculateShippingFee()
# 業務の言葉とコードが一致
ユビキタス言語は「業務用語辞書」ではありません。チーム全員が日常的に使う生きた言葉です。会議、コード、ドキュメントのすべてで同じ言葉を使い続けることが重要です。
境界付けられたコンテキスト(Bounded Context)
境界付けられたコンテキストとは、特定のドメインモデルが有効な範囲のことです。大きなシステムを論理的に分離し、それぞれのコンテキスト内で一貫したモデルを維持します。
なぜ境界づけられたコンテキストが必要なのか
同じ言葉でも、文脈によって意味が異なることがあります。境界づけられたコンテキストは、この問題を解決します。
「商品」という言葉の意味は文脈で異なる
────────────────────────────────────
注文コンテキスト:
商品 = 注文明細に含まれる購入対象
- 商品ID
- 商品名
- 価格
- 数量
在庫コンテキスト:
商品 = 倉庫で管理される在庫品
- 商品ID
- 在庫数
- 倉庫ロケーション
- 入荷予定日
カタログコンテキスト:
商品 = 顧客に表示される商品情報
- 商品ID
- 商品説明
- 画像
- カテゴリ
# 同じ「商品」でも、各コンテキストで持つべき情報が異なる
初学者向け:境界づけられたコンテキストの考え方
境界づけられたコンテキストは、「部署ごとの仕事の範囲」のようなものです。
現実世界の例
────────────
営業部門:
「顧客」= 売上を生む取引先
→ 契約金額、取引履歴、担当者情報
サポート部門:
「顧客」= サポートを提供する相手
→ 問い合わせ履歴、製品情報、対応状況
# 同じ「顧客」という言葉でも、部署ごとに関心事が異なる
コンテキストの分離例
各コンテキストは独立したドメインモデルを持ちます。
小規模なシステムでは、すべてを1つのコンテキストで扱っても問題ありません。コンテキストの分離は、システムが複雑になってから検討すれば十分です。
次のチャプターから、各概念を詳しく見ていきます。まずは値オブジェクトから始めましょう。
参考資料
DDDの基本概念についてさらに学びたい方は、以下の資料をご参照ください。
書籍
- Eric Evans「Domain-Driven Design: Tackling Complexity in the Heart of Software」(2003) - DDDの原典。すべての概念の詳細な説明があります
- 成瀬允宣「ドメイン駆動設計入門」- 日本語で学べる実践的なDDD入門書。値オブジェクトやエンティティの実装例が豊富です
オンラインリソース
- DDD Reference(Eric Evansの無料PDF) - ユビキタス言語、境界づけられたコンテキスト、集約などの重要概念をまとめたリファレンス
- Martin Fowler氏のブログ - DDDの各概念を簡潔に説明
DDDの概念は最初は難しく感じるかもしれませんが、実際にコードを書きながら学ぶことで理解が深まります。次章からの実装例を通じて、徐々に慣れていきましょう。