ドメイン駆動設計(DDD)における「エンティティ(Entity)」と「値オブジェクト(Value Object)」の構造的な違いと実装パターンについて整理する。
どちらもプロパティとメソッドを持つオブジェクトだが、識別子(ID)の有無や可変性(Mutability)において明確な役割の違いが存在する。
1. エンティティ(Entity)
エンティティは、識別子(ID)によって一意に識別されるオブジェクトである。内部の属性値(名前やメールアドレスなど)が変化しても、IDが同一であれば同一の存在とみなされる。プロパティを直接変更させず、明示的なメソッドを通して状態を更新する構造をとる。
// エンティティの定義:ユーザー(User)
export class User {
// ① 識別子(ID):個体を識別するための値。一度決定したら変更不可とする
readonly id: string;
// プロパティを private にして外部からの直接更新を禁止する(カプセル化)
private name: string;
private email: Email; // プロパティとして後述の「値オブジェクト(Email)」を保持
constructor(id: string, name: string, email: Email) {
this.id = id;
this.name = name;
this.email = email;
}
// ② 状態変更用メソッド:名前を変更する
// 外部からの直接更新(user.name = "" など)を許さず、このメソッド経由に制限する
changeName(newName: string): void {
// 【意図】不正なデータが入るのを防ぐガード節(業務ルールの強制)
if (newName.length === 0) {
throw new Error("名前を空にすることはできません");
}
// 名前が変わっても、this.id(識別子)は変わらない
this.name = newName;
}
// ② 状態変更用メソッド:メールアドレスを変更する
changeEmail(newEmail: Email): void {
// 値オブジェクトを受け取り、新しいオブジェクトに丸ごと差し替える
this.email = newEmail;
}
}
2. 値オブジェクト(Value Object)
値オブジェクトは識別子を持たず、保持する値そのものによって同一性が判断されるオブジェクトである。生成後の変更を認めない不変(Immutable)な構造とし、オブジェクト自身の生成時にバリデーションを実行して不正な値の存在を防ぐ。
// 値オブジェクトの定義:メールアドレス(Email)
export class Email {
// ① 保持する値:不変にするため readonly を付与し、作成後の変更を不能にする
readonly value: string;
constructor(value: string) {
// 【意図】生成時のバリデーション(自己防御)
// 不正な値(@がない文字列など)の場合、例外を投げてインスタンス生成自体を阻止する
if (!value.includes('@')) {
throw new Error("不正なメールアドレス形式です");
}
this.value = value;
}
// ② 同一性判定メソッド:値そのものが同じかどうかを判定する
// IDを持たないため、保持している文字列(value)同士が等しいかで判定する
equals(other: Email): boolean {
return this.value === other.value;
}
}
3. 構造上の違い
両者の主な構造上の違いは以下の通りである。
- 識別子(ID)の有無: エンティティは保持するが、値オブジェクトは保持しない。
- 可変性: エンティティは状態変更メソッドを持ち可変。値オブジェクトは生成後に変更不可(Immutable)。
- 構造の関係性: エンティティは個体を識別し、内部のプロパティとして値オブジェクトを保持・組み合わせることができる。
- 役割: エンティティは個体の識別と安全な状態遷移の管理。値オブジェクトは正しいデータ型とドメインルールの保証。