メインコンテンツまでスキップ

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;
}
}

// エンティティ: 注文
// 同時に Order集約の集約ルートでもある(OrderとOrderLineをまとめる)
class Order
{
private OrderId $id; // 識別子
private OrderStatus $status;
private Money $total;
private array $orderLines; // 集約内のエンティティ

// 可変: 状態が変わる
public function confirm(): void
{
$this->status = OrderStatus::CONFIRMED;
}

// 同一性: IDで判断
public function equals(Order $other): bool
{
return $this->id->equals($other->id);
}

// 集約ルート経由でのみアクセス
public function addItem(ProductId $productId, int $quantity): void
{
$this->orderLines[] = new OrderLine($productId, $quantity);
}
}

なお、この addItem() は概念を示すための簡略版です。明細に識別子や単価を持たせる形は第 6 章「エンティティ」で扱います。

使い分けの判断基準

判断例:

「金額」
識別子が必要? → NO
→ 値オブジェクト

「注文」
識別子が必要? → YES
独立して存在? → YES
他のオブジェクトと関連? → YES(注文明細と関連)
→ Order集約の集約ルート(注文と注文明細は一緒に管理する)

「注文明細」
識別子が必要? → YES
独立して存在? → NO(注文に依存)
→ Order集約内のエンティティ

集約(Aggregate)

集約はドメインオブジェクトではなく、**ドメインオブジェクトをまとめる構造(境界)**です。

集約のルール

  1. 外部からは集約ルート経由でのみアクセス
  2. 集約ルートが整合性を保証
  3. 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の概念は最初は難しく感じるかもしれませんが、実際にコードを書きながら学ぶことで理解が深まります。次章からの実装例を通じて、徐々に慣れていきましょう。