エンティティと値オブジェクト:違いの完全なリスト

ドメイン駆動設計のエンティティや値オブジェクトなどの概念の違いに関するトピックは新しいものではありません。 しかし、それらの違いの完全なリストを含む記事を見つけることができなかったので、自分で書くことにしました。



等価タイプ



エンティティと値オブジェクトの違いを示すには、3つのタイプの等式を定義する必要があります。これは、2つのオブジェクトを互いに比較しようとするとすぐに有効になります。



参照の等価性とは、2つのオブジェクトがヒープ上の同じオブジェクトを参照する場合に等しいことを意味します。



画像



C#で参照等価性をテストする方法は次のとおりです。



object object1 = new object(); object object2 = object1; bool areEqual = object.ReferenceEquals(object1, object2); //  true
      
      





識別子が等しいことは、クラスにIdフィールドがあることを意味します。 このクラスの2つのオブジェクトが同じ識別子を持っている場合、それらは等しくなります。



画像



最後に、 構造的等価とは、2つのオブジェクトのすべてのフィールドが完全に等価であることを意味します。



画像



エンティティと値オブジェクトの主な違いは、インスタンスを相互に比較する方法にあります。 識別子の等価性の概念はエンティティを指し、構造的等価性は値オブジェクトを指します 。 つまり、エンティティには固有のIDがありますが、値オブジェクトにはありません。



実際には、これは、値オブジェクトに識別子フィールドがないことを意味し、同じ値オブジェクトの2つのインスタンスが同じ属性セットを持っている場合、それらを交換可能と見なすことができます。 同時に、2つのエンティティのデータが完全に同一であっても(Idフィールドを除く)、それらは同じエンティティではありません。



同じ名前の2人の人と同じように考えることができます。 このため、私たちは彼らを同一人物とは見なしません。 どちらも内部(統合)アイデンティティを持っています。 同時に、1ルーブルがあれば、昨日と同じコインかどうかは気にしません。 このコインが1ルーブルのコインである限り、まったく同じものを別のコインに交換してもかまいません。 この場合のお金の概念は価値オブジェクトです。



ライフサイクル



2つの概念のもう1つの違いは、インスタンスのライフサイクルです。 エンティティは連続して生きています。 彼らには、彼らに何が起こったのか、そして彼らが人生を通してどのように変わったのかについての物語があります(この物語を続けていなくても)。



一方、値オブジェクトのライフサイクルはゼロです。 簡単に作成および破棄できます。 これは、それらが交換可能であるという事実から論理的に続く結果です。 ルーブルコインが他のルーブルコインとまったく同じ場合、違いは何ですか? 既存のオブジェクトを別のインスタンスに置き換えるだけで、その後は忘れることができます。



この違いから生じるGaidlanは、値オブジェクトが単独では存在できず、常に1つ以上のエンティティに属している必要があるという事実にあります。 オブジェクト値であるデータは、エンティティのコンテキストでのみ意味を持ちます。 上記のコインの例では、「いくらのお金ですか?」という質問は意味がありません。 十分なコンテキストがありません。 一方、「Petyaにはどのくらいのお金がありますか?」または「私たちのシステムのすべてのユーザーにどれくらいのお金がありますか?」という質問は完全に有効です。



ここでの別の結果は、値オブジェクトを個別に保存しないことです。 代わりに、データベースに保存するときにそれらをエンティティにインライン化(​​アタッチ)する必要があります(詳細については後述)。



不変性



次の違いは不変性です。 値オブジェクトは、そのようなオブジェクトを変更する必要がある場合、既存のインスタンスを変更するのではなく、既存のインスタンスに基づいて新しいインスタンスを作成するという意味で不変でなければなりません。 対照的に、エンティティはほとんど常に変更可能です。



値オブジェクトの必須の不変性は、すべてのプログラマーに受け入れられているわけではありません。 一部の人々は、このガイドラインは以前のガイドラインほど厳密ではないと考えており、値オブジェクトは場合によっては変更可能です。 私もしばらく前にこの意見を述べました。



現在、不変性と、ある値オブジェクトを別の値オブジェクトに置き換える能力との関係は、思ったよりも深いと考えています。 値オブジェクトのインスタンスを変更すると、ライフサイクルがゼロ以外になることを意味します。 そして、この仮定は、値オブジェクトが内部のアイデンティティを持っているという結論につながり、この概念の定義と矛盾します。



この単純なメンタルエクササイズにより、不変性は値オブジェクトの不可欠な部分になります。 ライフサイクルがゼロであり、ステートの単なるキャストであり、それ以上ではないという意味で受け入れる場合、このステートの1つのバージョンのみを表すことができることも受け入れる必要があります。



これにより、次のルールに導かれます値オブジェクトを不変にできない場合、このクラスは値オブジェクトではありません



ドメインモデルで値オブジェクトを認識する方法



ドメインモデルの概念がエンティティであるか値オブジェクトであるかは必ずしも明確ではありません。 残念ながら、これを判断できる客観的な属性はありません。 クラスが値オブジェクトであるかどうかは、操作するドメインドメインに完全に依存します。同じオブジェクトは、あるドメインではエンティティとして、別のドメインでは値オブジェクトとしてモデル化できます。



上記の例では、お金は交換可能と見なされます。 したがって、この概念は値オブジェクトです。 同時に、国内のすべての紙幣を追跡するシステムを作成する場合、各紙幣を個別に検討して統計を収集する必要があります。 この場合、お金の概念が本質になります。



客観的な指標が不足しているにもかかわらず、概念をエンティティまたは値オブジェクトに帰属させるために、いくつかの手法を使用できます。 すでに3種類の等価性について説明しました。クラスの1つのインスタンスを同じプロパティを持つ別のインスタンスに置き換えることができる場合、これはオブジェクト値があることを示す良い兆候です。



同じトリックの簡単なバージョンは、クラスを整数値(整数)と精神的に比較することです。 開発者としては、5という数字が前の方法で使用した数字と同じであるかどうかは関係ありません。 アプリケーションの5つはすべて、作成方法に関係なく同じです。 これにより、整数型は本質的に値オブジェクトになります。 今、自問してください:このクラスは整数のように見えますか? 答えが「はい」の場合、これは値オブジェクトです。



データベースに値オブジェクトを保存する方法は?



ドメインモデルにPersonエンティティとAddress値オブジェクトの2つのクラスがあるとします。



 // Entity public class Person { public int Id { get; set; } public string Name { get; set; } public Address Address { get; set; } } // Value Object public class Address { public string City { get; set; } public string ZipCode { get; set; } }
      
      





この場合、データベース構造はどのようになりますか? この状況で頭に浮かぶ解決策は、両方のクラスに別々のテーブルを作成することです。



画像



このような設計には、データベースの観点からの完全な妥当性にもかかわらず、2つの欠点があります。 まず、Addressテーブルには識別子が含まれています。 つまり、このようなテーブルを正しく操作するには、Addressクラスに個別のIdフィールドを入力する必要があります。 これは、クラスにアイデンティティを追加することを意味します。 そして、これはすでに値オブジェクトの定義に違反しています。



ここでの2番目の欠点は、エンティティの親から値オブジェクトを分離できる可能性があることです。 住所は、 対応するAddress行を削除せずにデータベースからPersonを削除できます。 これは、値オブジェクトの有効期間が親エンティティの有効期間に完全に依存する必要があるという別の規則に違反します。



この場合の最善の解決策は、AddressテーブルのフィールドをPersonテーブルに「インライン化」することです。



画像



これにより、両方の問題が解決されます。アドレスには独自の識別子がなく、その有効期間はPersonエンティティの有効期間に完全に依存します。



この設計は、先ほど提案したように、Addressに関連するすべてのフィールドを単一の整数に精神的に置き換える場合にも意味があります。 ドメインモデルの整数値ごとに個別のテーブルを作成しますか? もちろん、親テーブルに含めるだけではありません。 同じルールが値オブジェクトに適用されます。 値オブジェクト用に別個のテーブルを作成しないでください 。それらが属するエンティティのテーブルにフィールドを含めるだけです。



エンティティよりも値オブジェクトを優先する



次のルールは、値オブジェクトとエンティティの問題において重要です。常にエンティティよりも値オブジェクトを優先します。 値オブジェクトは不変であり、このため、それらを操作するのは非常に簡単です。 理想的には、ほとんどのビジネスロジックを値オブジェクトに組み込むよう常に努力する必要があります。 このような状況でのエンティティは、それらのラッパーとして機能し、高レベルの機能を表します。



また、エンティティとして最初に見た概念が実際に値オブジェクトである場合もあります。 たとえば、最初はコード内でAddressクラスをエンティティとして表すことができます。 データベースに独自のIDと個別のテーブルを含めることができます。 少し考えてみると、サブジェクト領域では、アドレスは実際には独自のIDを持たず、同じ意味で使用できます。 この場合、ドメインモデルをリファクタリングし、エンティティを値オブジェクトに変換することをnotしないでください。



おわりに





英語版の記事: Entity vs Value Object(DDD)



All Articles