エンティティと値オブジェクト
読了時間: 約10分
はじめに
エンティティ(Entity)と値オブジェクト(Value Object)は、Prism Architectureにおけるドメインモデルの基盤を形成します。コア層に位置するこれらの要素は、アプリケーションの本質的なビジネス概念を表現します。これら2種類のドメインオブジェクトの違いを理解することは、ビジネスドメインを正確に表現する堅牢なドメインモデルを構築する上で重要です。
この文書では、エンティティと値オブジェクトの主要概念、その特徴、違い、そしてPrism Architecture内でそれぞれをいつ使用するかについて説明します。
基本概念
エンティティとは?
エンティティとは、属性が変化しても一貫した固有のアイデンティティ(ID)を持つドメインオブジェクトです。継続性とアイデンティティを持つドメイン内の「もの」を表します。
エンティティの主な特徴:
-
アイデンティティによって定義される: エンティティは主に属性ではなく、一意の識別子によって識別されます。同じ属性を持つ2つのエンティティでも、識別子が異なれば別のエンティティとみなされます。
-
可変または不変: エンティティはどちらでも実装可能ですが、予測可能性を高め、予期しない副作用を防ぐために可能な限り不変性が好まれます。
-
継続的なアイデンティティ: エンティティはすべての属性値が時間の経過とともに変化しても、同じエンティティであり続けます。人は名前、住所、その他の属性が変わっても同じ人のままです。
-
ライフサイクル: エンティティには明確なライフサイクルがあります—作成され、時間の経過とともに変化し、アーカイブまたは削除される可能性があります。
-
ビジネスルール: エンティティはしばしば、その振る舞いと状態変化を管理するビジネスルールをカプセル化します。これらのルールはエンティティの整合性を維持するのに役立ちます。
エンティティの例:
- ユーザー/顧客
- 注文
- 商品
- アカウント
- 予約
値オブジェクトとは?
値オブジェクトは独自のアイデンティティを持たないドメインオブジェクトです。完全に属性によって定義され、ドメインの側面を説明、定量化、または測定するために使用されます。
値オブジェクトの主な特徴:
-
属性によって定義される: 値オブジェクトはすべての属性値の組み合わせによって識別されます。同じ属性を持つ2つの値オブジェクトは、メモリ上の位置に関係なく同等とみなされます。
-
不変: 作成後、その属性は変更されるべきではありません。異なる値が必要な場合は、新しい値オブジェクトインスタンスを作成します。
-
アイデンティティなし: 値オブジェクトには一意の識別子がなく、同じ属性を持つなら交換可能です。
-
自己完結型バリデーション: 値オブジェクトは作成時に属性を検証し、有効な概念を表していることを保証します。
-
概念的な全体: ドメイン内の完全な概念を表し、単なるプリミティブ値ではありません。
値オブジェクトの例:
- 金額/通貨
- 住所
- 日付範囲
- 座標
- 人名
- メールアドレス
エンティティと値オブジェクト: 主な違い
特性 | エンティティ | 値オブジェクト |
---|---|---|
アイデンティティ | アイデンティティによって定義 | 属性によって定義 |
可変性 | 多くの場合可変、不変も可能 | 常に不変 |
等価性 | アイデンティティに基づく | 属性値に基づく |
ライフサイクル | 明確なライフサイクルがある | 独立したライフサイクルなし |
置き換え | その場で更新 | 完全に置き換え |
例 | ユーザー、注文、商品 | 金額、住所、日付範囲 |
Prism Architectureでの実装
どこに位置するか?
Prism Architectureでは、エンティティと値オブジェクトはコア層に存在し、他のすべての層からアクセス可能です。この配置により、アプリケーション全体で共有される語彙として機能します。
エンティティ設計パターン
エンティティの実装を強化するいくつかの設計パターンがあります:
- 型付き識別子
エンティティIDにプリミティブ型を使う代わりに、値オブジェクトを使用して型安全な識別子を作成します。このアプローチは異なる種類のIDの混同を防ぎ、コードの自己文書化を高めます。
- エンティティファクトリーメソッド
エンティティ作成時に不変条件を強制するファクトリーメソッドを作成します。ファクトリーメソッドは、作成ロジックをカプセル化しながら、有効なエンティティインスタンスを作成する明確で一貫した方法を提供します。
- 変更メソッドを持つ不変エンティティ
変更が必要な場合に新しいインスタンスを返すメソッドを持つ不変構造としてエンティティを実装します。このパターンは、不変性の利点と時間とともに変化するエンティティをモデル化する必要性を組み合わせます。
値オブジェクト設計パターン
値オブジェクトはこれらの設計パターンから恩恵を受けます:
- 一般的なインスタンス用ファクトリーメソッド
コードの可読性を向上させ、重複を減らすために、よく使用されるインスタンス用のファクトリーメソッドを提供します。例えば、Moneyクラスはゼロ値や一般的な通貨のファクトリーメソッドを提供するかもしれません。
- 値オブジェクト操作
ドメイン操作を値オブジェクトのメソッドとして実装します。これらの操作は、既存のものを変更するのではなく、新しい値オブジェクトを作成し、不変性を尊重します。
- 値オブジェクト変換
関連する値オブジェクト間で変換するメソッドを提供します。例えば、温度値オブジェクトには摂氏と華氏の間で変換するメソッドが含まれるかもしれません。
一般的なパターンとベストプラクティス
エンティティのベストプラクティス
-
最小限のパブリックAPI: 有効なビジネス操作を表すメソッドのみを公開します。
-
ビジネスルールのカプセル化: 状態変更を管理するビジネスルールはエンティティに含めるべきです。
-
アイデンティティ生成戦略: エンティティIDの生成方法(クライアント側 vs サーバー側)に一貫性を持たせます。
-
バリデーション: 作成時だけでなく、状態変更に対するバリデーションロジックも含めます。
-
振る舞い重視: データストレージよりも振る舞いに焦点を当てます。
-
永続化を意識しない: エンティティは自身がどのように永続化されるかを知るべきではありません。
値オブジェクトのベストプラクティス
-
真の不変性: 値オブジェクトが作成後に変更できないことを確保します。
-
自己バリデーション: 作成時にすべての属性を検証します。
-
ドメイン的に意味のある操作: ドメイン操作を表すメソッドを実装します。
-
最小限の依存関係: 値オブジェクトは依存関係がほとんどまたは全くないべきです。
-
明確なセマンティクス: 表現される概念を明確に伝える名前を選びます。
-
値による等価性: 等価性がすべての属性を比較することを確保します。
避けるべき一般的なアンチパターン
-
貧血ドメインモデル: 振る舞いがなく、ゲッターとセッターだけのエンティティと値オブジェクト。
-
アイデンティティの混乱: 概念的に値オブジェクトであるものをIDがあるからといってエンティティとして扱うこと。
-
可変値オブジェクト: 値オブジェクトが作成後に変更可能であること。
-
プリミティブ型執着: ドメイン概念に値オブジェクトではなくプリミティブ型を使用すること。
-
過負荷エンティティ: あまりにも多くのことを行おうとしたり、あまりにも多くの概念を表現しようとするエンティティ。
他の層でのエンティティと値オブジェクト
エンティティと値オブジェクトはコア層で定義されますが、他の層と特定の方法で相互作用します:
ドメイン層
- ビジネスロジックを実装するためにエンティティと値オブジェクトを使用
- ドメイン固有のメソッドでエンティティを拡張することがある
- 値オブジェクトを使用してエンティティの操作を検証
オーケストレーション層
- エンティティの操作を調整
- エンティティのライフサイクルを管理
- 必要に応じてエンティティとDTOの間でマッピング
インフラストラクチャ層
- エンティティの永続化を実装
- エンティティと外部データ形式の間で変換
- 通常、エンティティの一部としてを除き、値オブジェクトと直接連携しない
プレゼンテーション層
- エンティティデータをユーザーに表示
- 表示のために値オブジェクトをフォーマット
- エンティティを作成または変更するためのユーザー入力を収集
エンティティと値オブジェクトのテスト
エンティティのテスト
-
アイデンティティテスト: 等価性が属性ではなくアイデンティティに基づいて機能することを検証します。
-
振る舞いテスト: ビジネスルールとメソッドが正しく機能することをテストします。
-
ライフサイクルテスト: エンティティが状態変化を通じてそのアイデンティティを維持することを確認します。
値オブジェクトのテスト
-
等価性テスト: 等価性がすべての属性に基づいて機能することを検証します。
-
バリデーションテスト: 無効な状態が拒否されることをテストします。
-
操作テスト: ドメイン操作が正しい結果をもたらすことをテストします。
-
不変性テスト: 値オブジェクトが作成後に変更できないことを確認します。
実世界の例
エンティティの例: 注文
Eコマースシステムにおける注文エンティティには次のものがあります:
- 他のすべての注文と区別する一意の注文ID
- 顧客情報、配送先住所、注文項目などのプロパティ
- アイテムの追加または削除、割引の適用、合計金額の計算などのメソッド
- 最低注文金額や配送制限などの制約を強制するビジネスルール
- 作成から処理、発送、完了までの明確なライフサイクル
値オブジェクトの例: 金額
金融アプリケーションにおける金額値オブジェクトには次のものがあります:
- 金額と通貨のプロパティ
- アイデンティティなし—10米ドルは他のどの10米ドルとも等しい
- 不変性—お金を変更するのではなく、異なるお金と交換する
- 加算、減算、パーセンテージ計算などの操作
- 有効な通貨と金額で適切に初期化されることを確保するバリデーション
結論
エンティティと値オブジェクトはPrism Architectureにおける堅牢なドメインモデルの基盤を形成します。どの概念がエンティティであり、どの概念が値オブジェクトであるかを正しく識別することで、ビジネスドメインを正確に反映し、アプリケーションの残りの部分のための堅固な基盤を提供するドメインモデルを作成します。
コア層の他のすべての層へのアクセス性により、これらのドメイン概念はアプリケーション全体で共有される語彙となり、一貫性を確保し、層間の変換の必要性を減少させます。
この文書で説明されているパターンとベストプラクティスに従うことで、ドメイン概念を正確に表現するだけでなく、ビジネスルールを適用し、データの整合性を維持し、Prism Architectureの中心である明確な関心の分離をサポートするエンティティと値オブジェクトを作成することができます。
次のステップ
- ドメインサービス - エンティティに対して操作するビジネスロジックの実装方法を学ぶ
- リポジトリとデータアクセス - エンティティの永続化と取得方法を理解する
- ユースケースとビジネスロジック - ユースケースでエンティティと値オブジェクトがどのように使用されるかを確認する