私は最近、Always Validドメインエンティティについて多くの読書をしました。エンティティが常に有効であることを保証するために、私は次のことを行う必要があると信じるようになりました。
1)ここで説明されているように、プリミティブな強迫観念を取り除き、値オブジェクトコンストラクターに検証/ドメインルールを追加します https://enterprisecraftsmanship.com/2016/09/13/validation-and-ddd/ 。 2)ここで説明されているように、検証またはドメインルールをエンティティまたはプロパティセッターのコンストラクタに配置します: http://gorodinski.com/blog/2012/05/19/validation-in-domain-driven-design- ddd / 。
ただし、次に、このような https://github.com/gregoryyoung/m-r などのいくつかのオープンソースプロジェクトを調べます。私が理解していることから、このプロジェクトの作成者は常に有効なドメインモデルの擁護者ですが、ここでInventoryItemクラスを調べます。 https://github.com/gregoryyoung/mr/blob/master/SimpleCQRS/ Domain.cs 。私はこれを行うことができることに気づきました:
InventoryItem inventoryItem = new InventoryItem();
またはこれ:
InventoryItem inventoryItem2 = new InventoryItem(Guid.Empty,null);
私の考えでは、これはエンティティが無効な状態で初期化されることを意味します。これは、私が最近見た他のすべてのオープンソースプロジェクトにも当てはまるようです。これ: https://github.com/dcomartin/DDD-CQRS-ES-Example/blob/master/src/Domain/Customer.cs 。
これらのオープンソースプロジェクトにコンテキスト検証があることを理解しています( https://martinfowler.com/bliki/ContextualValidation.html )。また、ドメインモデルにマップする場合、ORMにはデフォルトの空のコンストラクターが必要であることも理解しています。
ゼロ引数コンストラクターを使用してデフォルト値で初期化されている/空/ null値で初期化されている場合、ドメインオブジェクトは有効な状態にありますか?
オブジェクトの作成に関する考慮事項について説明する前に、まず質問の背後にある動機、「常に有効なドメインエンティティ」というフレーズについて説明しましょう。このフレーズは、せいぜい誤解を招くだけで、DDDのコンテキストではほとんど意味がありません。これは、2つの関連する理由から明らかです。
1つ目は、システムの動作から焦点を暗黙的に移すことです。代わりに、状態の観点からのみ検証を検討するように求めます。これは表面的には理にかなっているように見えるかもしれませんが(もちろん、検証は状態の観点からです!)、DDDの基本原理は、システムが動作に従ってモデル化されていることを覚えておく必要があります。これの動機は、コンテキストまたはビジネスプロセス自体が、状態の一部が有効であるかどうかを判断するときに重要な考慮事項であることが多いことです。この方法でシステムをモデル化すると、システムの複雑さを大幅に減らすことができます。
これは、そのようなシステムが必要とする実際的な要件に関して、2番目の理由をもたらします。 「常に有効なドメインエンティティ」のシステムを作成するには、状態が使用されるビジネスプロセスに従って、状態のすべての順列をモデル化する必要があります。簡単な例で、この制限を説明できます。
ルール:
Customer
は18を超えて登録する必要がありますCustomer
は、登録の割引を受けるには25歳未満である必要がありますCustomer
が25歳以上である必要があります最初に気付くべきことは、これらのすべてのルール(ほぼすべてのルールと同様)がいくつかのビジネスプロセスに適用されることです。それらは孤立して存在しません。これらのルールは、customer.Register()
およびcustomer.Reserve()
で検証されます。これにより、ルールがどこで実行されているかが明確になるため、はるかに記述的で宣言的なパラダイムになります。
「常に有効なドメインエンティティ」のシステムになるようにこれらのルールをモデル化したい場合、Customer
をRegistrar
およびReserver
エンティティに分割する必要があります。そして、この例ではそれほど悪くないように思えるかもしれませんが、ルールがより複雑で豊富になると、いくつかのコンテキストまたはプロセスの「内部」の状態を表すこのような爆発クラスになります。これは単に不要であり、そのような2つのオブジェクトが同じ状態のスライスに依存している場合、必然的に問題が発生します。
さらに、Customer c = new Customer()
のようなものは、適用されるビジネスルールが明確でないため、例外をスローするのに適していません。これで、最終的な議論が始まります。
私は出てきて、コンストラクタで発生するビジネスルールのzero検証があるべきだと言って遠くまで行くつもりです。オブジェクトの構築はビジネスドメインとは何の関係もありません。そのため、コンテキストと一貫性に関する上記の理由に加えて、すべてのビジネスルールはエンティティのメソッド本体内で適用する必要があります(メソッドはビジネスプロセスにちなんで名付けられている可能性があります) ?)。
さらに、オブジェクトを「更新する」ことは、ドメインに新しいエンティティを作成することとはではありません。オブジェクトはどこからともなく出てくるわけではありません。新しいエンティティをシステムに取り込む方法に関するビジネスルールがある場合は、ドメイン内でモデル化する必要があります。ここでは、真のマスターによるトピックについてのさらなる議論があります http://udidahan.com/2009/06/29/dont-create-aggregate-roots/
ゼロ引数コンストラクターを使用してデフォルト値で初期化されている/空/ null値で初期化されている場合、ドメインオブジェクトは有効な状態にありますか?
かもね。
デフォルトのコンストラクターを使用して有効なドメインオブジェクトを作成することについて、規則に対しては何もありません。
空の値を使用して有効なドメインオブジェクトを作成することについてルールに対してはありません。
オプションの要素が欠けている有効なドメインオブジェクトの作成についてはルールに対して何もありません。
Nullを使用して有効なドメインオブジェクトを作成することについてルールに対してはありません。
問題が発生する場所:独自のセマンティック代数に従わないドメインオブジェクトの作成。
正しい実装は、セマンティクスの正しい解釈に依存します。
ドメイン概念の寛容表現を取り、ステップごとに追加の制約を適用することは通常のことです。これは、型の追加を容易にする実装言語(例:F#)がJavaのような扱いにくい言語よりも優れている領域の1つです。
たとえば、数値を重視するドメインがあり、そのドメイン内に素数またはメルセンヌ素数に固有のいくつかの機能がある場合、使用する自然なメカニズムはthree異なるタイプを作成することです;ソリューションのさまざまな部分の入力に適用される制約のさまざまなセットがあるという概念を明示します。
Number n = new Number(...)
Optional<Prime> p = n.prime()
if (p.isPresent()) {
Optional<MersennePrime> mp = p.mersennePrime()
// ...
}
この種の設計では、数値が本当に素数であるという「検証」は、素数コンストラクタ自体の中に存在します。パラメータがMersenne素数であるという追加の制約が必要な状況では、Prime
-> MersennePrime
変換を使用して、メルセンヌの知識が制約には1つの権限があります(「自分を繰り返さないでください」)。
「暗黙的、明示的にする」