エンティティと値オブジェクト

DDD における 2 つの基本的なドメインモデル要素 - 同一性で区別するエンティティと、値で区別する値オブジェクト

DDD設計

エンティティと値オブジェクトとは

エンティティ (Entity) と値オブジェクト (Value Object) は、ドメイン駆動設計 (DDD) における 2 つの基本的なモデル要素である。エンティティは同一性 (ID) で区別し、値オブジェクトは属性の値で区別する。

比較

観点 エンティティ 値オブジェクト
同一性 ID で区別 値で区別
可変性 可変 (状態が変わる) 不変 (イミュータブル)
ライフサイクル 生成 → 変更 → 削除 生成 → 破棄 (変更は新規作成)
等価性 ID が同じなら同一 全属性が同じなら等価
ユーザー、注文、商品 メールアドレス、金額、住所

TypeScript での実装

エンティティ

class User {
  constructor(
    readonly id: string,
    private name: string,
    private email: Email, // 値オブジェクト
  ) {}

  changeName(newName: string) { this.name = newName; }

  equals(other: User): boolean {
    return this.id === other.id; // ID で比較
  }
}

値オブジェクト

class Email {
  readonly value: string;

  constructor(value: string) {
    if (!value.includes('@')) throw new Error('Invalid email');
    this.value = value; // バリデーション + 不変
  }

  equals(other: Email): boolean {
    return this.value === other.value; // 値で比較
  }
}

class Money {
  constructor(readonly amount: number, readonly currency: string) {}

  add(other: Money): Money {
    if (this.currency !== other.currency) throw new Error('Currency mismatch');
    return new Money(this.amount + other.amount, this.currency); // 新しいインスタンスを返す
  }

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

判断基準

質問 エンティティ 値オブジェクト
ID で追跡する必要があるか?
属性が変わっても同じものか? ✅ (名前が変わっても同じユーザー)
交換可能か? ✅ (同じ値なら交換可能)

DynamoDB での表現

// エンティティ: ID で管理
{
  "pk": "USER#123",
  "sk": "PROFILE",
  "name": "Alice",
  "email": "alice@example.com",  // 値オブジェクトは属性として埋め込む
  "address": {                    // 値オブジェクト
    "prefecture": "東京都",
    "city": "渋谷区"
  }
}

値オブジェクトは独立したアイテムにせず、エンティティの属性として埋め込む。

よくある間違い

  • 全てをエンティティにする → 値オブジェクトにすべきものまで ID を振る
  • 値オブジェクトを可変にする → バグの温床になる
  • プリミティブ型で済ませる → string ではなく Email 型にすることでバリデーションを強制

実践的な知識は関連書籍でも得られる。

関連用語