web-dev-qa-db-ja.com

集合体の内部エンティティは、ドメインイベントに直接応答する必要がありますか?

ドメイン駆動設計のルールを適用しながら、CQRSとイベントソーシングを実装するのはかなり新しいです。 Order集約があり、その中にOrderLineエンティティのリストがあるとします。私はこのモデルをイベントソースシステム(CQRSを使用)のコンテキストで使用しているため、状態の変化は主にイベントによって駆動されます。アグリゲートはアグリゲートルートとして機能するため、アグリゲートへの変更はOrderを通じて実行する必要があります。たとえば、ItemAddedのようなイベントが発生すると、order.apply(itemAdded)のように、それぞれのOrder集合体が呼び出され、それ自体に適用されます。これにより、新しい内部OrderLineオブジェクトの作成がトリガーされ、新しく追加されたアイテムが注文に反映されます。実装に関して、新しいOrderLineオブジェクトがイベントをそれ自体に直接適用することは理にかなっていますか?または、コンストラクタを介してOrderオブジェクト自体を作成するために、それをOrderLine集合体にそのまま残すべきですか?

どちらがより適切ですか?

_void apply(ItemAdded itemAdded) {
    OrderLine orderLine = new OrderLine();
    oderLine.apply(itemAdded);
    orderLines.add(orderLine);
}
_

または

_void apply(ItemAdded itemAdded) {
    OrderLine orderLine = new OrderLine(
        itemAdded.getId(), 
        itemAdded.getProductId(), 
        itemAdded.getQuantity(), 
        itemAdded.getPrice()
    );
    orderLines.add(orderLine);
}
_

今のところ、最初のアプローチの方が簡潔で理解しやすいと思います。フィールドが非常に長くなる可能性のあるイベントがあり、内部エンティティ自体が構造を処理するようにすると、コードがよりクリーンになります。しかし、問題は、applyOrderLineメソッドを提供することで、Orderのコンテキスト外での突然変異の可能性のためにそれを開いていることだと思います。 OrdergetOrderLines()などのアクセサーを提供すると、コレクション/リスト自体を変更不可にしても、含まれるオブジェクトは変更可能になります。これを防ぐ1つの方法は、クローンを返すことですが、かなり複雑なネストされたエンティティに対して実装するのが少し面倒になる可能性があります。

これに対処するには、applyクラスのOrderLineメソッドを維持し、その可視性を制限することを考えています。この点で私に最も柔軟性を与えるアプローチは、OrderLineOrderクラスの下のネストされたクラスにすることです。これは、applyメソッドをプライベートに保ちながら、Orderクラスのコンテキスト内でアクセスできることを意味します。ただし、これは、特にネストされたエンティティを今後追加する必要がある場合は特に、非常に長いOrderクラスを意味する可能性があります。

または、applyメソッドへのアクセスをパッケージレベルで制限することもできます。つまり、1つのパッケージ内のすべてのドメインクラスを制限するために、いくつかの再構築を行う必要があります。

これに関するアドバイスは?

6
Psycho Punch

私たちがあなたの質問を額面のみで受け止める場合、答えはapply()メソッドを提供する代わりにitemAddedオブジェクトをOrderLineのコンストラクターに渡すことです。このように、2つの例の唯一の違いは、オブジェクト全体を単一のパラメーターで渡すのか、そのプロパティを複数のパラメーターとして渡すのかです。

ただし、itemAddedが実際にはイベントであり、アイテムではない場合は、itemAddedEventのような名前に変更する必要があります。また、Itemを表すItemクラスを用意し、それをイベントから構築することも検討できます。次に、ItemをOrderLineに渡すことができます。これにより、ビジネスの専門家や顧客がプロセスについて話し合う方法に、ドメインが一致するようになります。

2
RibaldEddie

あなたはあなたのデザインで一つの間違ったことを持っています。ドメインではなく、リレーショナルデータベースに従ってモデリングしているようです。なぜそう思いますか? Order集約ルートがあり、これには内部にOrderLine保持Itemsが含まれています。 OrderLineクラスを使用してM:N関係をモデル化しました。

重要なのは、DDDを使用する場合、データに一致するようにモデル化しないため、構造もリレーショナルデータベースを反映しないようにする必要があります。

次のことを自問してください。

  1. ビジネスは何を気にしていますか?
  2. アイテムを注文にマップするために、OrderLineと呼ばれる追加の(ほとんどの場合は弱い)エンティティが必要なビジネスケアはありますか?
  3. マッピングOrderLineテーブルからレコードを追加/削除する必要がありますか?

質問2と3への回答はノーです。ビジネスマネージャーは、関係をどのようにモデル化するかをそれほど気にする必要はありませんが、統計に関しては、次の点に注意する必要があります。

  • 平均していくつのアイテムにOrderがあり、完全に処理されますか?
  • ItemOrderに追加してItemOrderから削除する回数はどれくらいですか?
  • 完全に処理されるOrderの平均コストはいくらですか?
  • ユーザー注文 sは平均でいくつありますか?
  • ItemOrderから削除されて、再度追加された回数は何回ですか?

これらはすべて将来的にビジネス部門から来るかもしれない有効な質問です。これらの各質問で、影響を受ける集計を強調表示しました。ご覧のとおり、エンティティは実際に関係をモデル化しているだけなので、OrderLineはまったくありません。あなたのビジネスはあなたのItem sとOrder sを気にしているので、あなたもそうするべきです。

これを念頭に置いて、OrderLineは、実際には次のような非常に単純な値のオブジェクト/エンティティを含む単純なリスト/ベクトル/セットである必要があります。

class OrderLineItem extends Entity {
    private ItemId itemId;
    private int quantity = 1;

    public bool Equals(Entity other) {
        return
          other instanceof OrderLineItem &&
          itemId.Equals(other.itemId);
    }
}

Itemは、他の集計を直接保持するべきではないため、IDによってのみ参照されます。

このアプローチでわかるように、エンティティがなくなっただけなので、OrderLineがイベントを直接受け入れるかどうかを気にする必要はありません。


イベントソースシステムでは、データストアは関係ではなくイベントであるため、M:N関係テーブルはまったく存在しません。

クエリ側のイベントから読み取りモデルを構築する場合、読み取りモデルはできるだけフラットでなければなりません。理想的には、SELECT * FROM read_model WHERE ... 何もありません。参加していません。したがって、読み取り側にもM:N関係がない可能性が非常に高くなります。正直なところ、読み取りモデルはとにかくドキュメントであるため、MongoDBなどのより適切な別のデータベースを使用する方がはるかに理にかなっています。

1
Andy

どちらがより適切ですか?

私は最初のアプローチが長期的にはより健康になるものであるとほぼ確信しています。

謎:OrderLineに対する企業の理解がドメインモデルの存続期間中に変更されるとしたら、どの設計が扱いやすくなるでしょうか?

最初のケースでは、ItemAddedメッセージを拡張して必要な新しいフィールドを含め、OrderLineアイテムを更新してそれらの新しいフィールドを読み取り、興味深いことを行います。それだけです。

2番目のケースでは、ItemAddedメッセージを拡張する必要があり、OrderLineを変更する必要があります。また、また必要があります。 Orderを変更します。それはあまり良く聞こえません。

Parnasが ...システムをモジュールに分解

代わりに、難しい設計決定または変更される可能性のある設計決定のリストから始めることを提案します。各モジュールは、そのような決定を他のモジュールから隠すように設計されています。

この例では、OrderLineは、「ItemAddedメッセージをどのように使用するか」という決定を隠しているモジュールです。

オーダーのコンテキスト外での突然変異の可能性のために、それを開放しています。アクセサ、たとえばgetOrderLines()をOrderで提供した場合、コレクション/リスト自体を変更不可にしても、含まれているオブジェクトは変更可能です。これを防ぐ1つの方法は、クローンを返すことですが、かなり複雑なネストされたエンティティに対して実装するのが少し面倒になる可能性があります。

Command Query Separation および Role Interfaces が役立ちます。大まかな考え方は、OrderItemを、さまざまな役割で実行できるものと考えることです。そのため、OrderItemが実装するインターフェースは異なるスライスに分割され、その1つはIApplyEventsまたはIHandle<ItemAdded>、そしてアイテムの変更を許可するロールが必要なユースケースでのみ公開されるようにロジックを設計します。

したがって、getOrderLinesのようなアクセサがある場合、それは完全にクエリで構成されるRoleInterfacesのコレクションを返します。

interface OrderLineDetails {
    ProductDetails getProductDetails();
}

interface IHandle<Event> {
    void apply(Event event);
}

class OrderLine implements OrderLineDetails, IHandle<ItemAdded> {
    // ...
}

名前は乱雑になる可能性があります。誰もが「オーダーライン」がその特定のコンテキストでそれが何を意味するかを意味することを望んでいます。 DependencyInversion が役立ちます。

これを防ぐ1つの方法は、クローンを返すことですが、かなり複雑なネストされたエンティティに対して実装するのが少し面倒になる可能性があります。

場合によっては、かなり複雑なネストされたエンティティが本当に複雑なネストされた値であり、物事を簡素化することがあります。

State next = current.apply(change)

かなり合理的です。要約すると、とにかく。アイデアからどのような複雑さが生じているのか、そしてたまたま使用しているツールから何が生じているのかを認識するように注意してください。 問題によりよく適合する が必要なヒントがあるかもしれません。

1
VoiceOfUnreason

そこにルールはありません。それは非常に依存します:-永続モデル-CQRSを適用する場合-エンティティの複雑さと不変量。

私は通常、同じAR内のESインターフェイスにイベントを自然にカスケードするAR&ESの抽象化に依存しています。

しかし、ES集約ルートのほとんどは小さく、非常に「シンプル」です。VOのみでそれらを設計しようとします。通常、ARは別のエンティティの状態を変更するべきではありません。

あなたの例では、私はOrderLineをESしません。 OrderLineが状態を変更するときに注意して適用するかなり複雑なルールがない限り。現在のビジネス行動を考慮して、アイテムを追加します。 OrderLineをVOとして設計します。

注文をESしているので、ESを使用する方法が見つかりません。従来のeコマースのライフサイクルを常に追跡できます。

ESのカスケードは、集合体が非常に複雑であることを意味します。そこに進む前に、設計に質問してください。

0
ludofleury