エンティティと値オブジェクト
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型にすることでバリデーションを強制
実践的な知識は関連書籍でも得られる。