web-dev-qa-db-ja.com

DDD-複雑な集合体での単純な操作の実行

私はDDDブルーブックを読んでいますが、現時点ではまだはっきりしないことが1つあります。それは、多数の子エンティティが含まれているAggregatesに対して単純な操作を効率的に実行する方法です。

  • _Line Item_ sのあるOrder、(Aggregate Root)があることを考慮してください。
  • Orderには、1000個の_Line Item_ sのみを含めることができます。 (ビジネスルール)。

手順

_Line Item_をOrderに追加するには、次のようにする必要があります。

  • Orderallで再構成します。リポジトリの_Line Item_ sです。
    • orderRepo.getById(1)
  • _Line Item_をOrder、に追加します
    • order.addLineItem(lineItem)
  • リポジトリを介してOrderを再度保存します。
    • orderRepo.save(order)

私が何かを逃していない限り、これは私にひどく非効率的であると思います。そのOrderに900 _Line Items_がある場合はどうなりますか?この単純な操作を実行するには、DBから大量のデータをロードする必要があります。

はい、LineItemsを遅延ロードできますが、それでも、すべての_Line Items_がロードされない限り、<1000 _Line Items_がOrderに存在する必要があることを要求するビジネスルールをチェックできません。

また、遅延読み込みの実装は、通常、プロパティアクセサーとリポジトリを結び付けるため、アンチパターンとして私を攻撃します。

3
Nik Kyriakides

DDDを使用すると、ドメインモデルを改善するためにパフォーマンスが低下します。そして、これはその妥協が行われるかもしれない領域です。

あなたの問題を見て、LineItemOrder集合体の一部である必要があるかどうか質問します。集計のポイントは、複数の複雑な操作が行われるエンティティをグループ化することです。内部エンティティと10の操作を持つ集計があるとします。 1つのケースでは、9つの操作がエンティティで機能します。そのような場合、エンティティが常に読み込まれているとは限らない場合、状況は大幅に複雑になります。一方、これらのエンティティが必要な操作が1つだけの場合は、それらのエンティティを集合体の一部にする必要があるかどうかを質問します。操作が複雑な場合、ケースは比較的単純です。アイテムのカウントを取ることは、DB側で実行できる簡単な操作です。しかし、制限が価格ではなく、数ではなかったと想像してみてください。この場合、複雑な「価格計算」ロジックを実行する必要があります。これには、おそらくすべてのデータを持つすべての注文アイテムが必要になります。つまり、広告申込情報を読み込まないことはできません。

2
Euphoric

あなたの問題は、実際のItemオブジェクトをOrder集合体に保持する必要があると考えていることだと思います。それは間違いです。これを行うと、メモリのフットプリントは非常に狂ってしまい、パフォーマンスはひどいものになります。また、1つの操作(コマンド)は1つの集合体にのみ影響を与えるため、これを行うべきではありません。これにより、コードの一貫性と簡潔さが同時に向上します。

より高いパフォーマンスと一貫性を維持するために、実際にはLineItemOrderItemの両方の識別子への参照のみを保持する非常に単純なオブジェクトです。

_final class LineItem {

    private final OrderId orderId;

    private final ItemId itemId;

    public LineItem(OrderId orderId, ItemId itemId) {
        this.orderId = orderId;
        this.itemId  = itemId;
    }
}
_

ItemOrder集合体に追加すると、次のようにモデル化されます。

_public class Order {

    private static final int MAX_CAPACITY = 1000;

    private final OrderId orderId;

    private final List<LineItem> lineItems = new ArrayList<>();

    public void addItem(Item item) {
        if (lineItems.size() == MAX_CAPACITY) {
            // throw exception
        }

        lineItems.add(new LineItem(orderId, item.itemId()));
    }
}
_

これからデータをどのように読み取るのか疑問に思っている場合、答えは、あなたがする必要はなく、通常はそうしないことです。集合体はサイズが大きくなる可能性がありますが、これはビジネスルールの一貫性と可視性を維持するために有利です。読み取りとパフォーマンスのためには、データベースと直接通信する別のレイヤーが必要であり、完全に集約を回避します。これを行うことができるのは、データベース内のすべてのデータが有効であることがわかっているためです。すべての変更は、ルールを保護する集約を通じて行われるためです。

この問題を解決する別のオプションは、他の人がすでに示唆しているように、OrderRepositoryGetItemsCount(OrderId)を含めることです。それでも、あなたの主な問題は、集計を誤って参照することを考えることだと思います。

集計モデリングの非常に優れたソースを検討している場合は、必ずVaugh Vernon氏による Effective Aggregate Design を参照してください。

2
Andy
  1. GetOrderItemsCount()と呼ばれるメソッドをリポジトリに追加し、それに興味のある注文のIDを渡し、現在注文にあるアイテムの数を返します。これはビジネスルールを満たし、必要に応じて最適化できます。

  2. 新しいアイテムを追加するために既存のアイテムをすべてロードする必要はありません。特に、現在のカウントがすでにわかっており、ビジネスルールに対してチェックしている場合は特にそうです。

  3. 遅延読み込み?遅延読み込みとは何ですか?

1
Robert Harvey

DDDでは、コマンドを適用する前に、リポジトリからAggregate全体をロードする必要があります。完全にロードする必要がないと思う場合は、Aggregate境界が間違っている可能性があります。Aggregateが大きすぎる可能性があります。アグリゲートはトランザクションの境界であることを覚えておいてください。アグリゲート内で発生するすべてのことは同時更新から安全であり、操作には強い整合性があります。これはsafe-nessはいい方法ですが、コストがかかるため、実際のビジネスに従って実行する必要があります。

また、遅延読み込みの実装は、通常、プロパティアクセサーとリポジトリを結び付けるため、アンチパターンとして私を攻撃します。

はい、Aggregate設計のコンテキストでは、遅延読み込みはアンチパターンです。これは、集計境界が間違っている(最も可能性が高い)ことを意味します。

各注文に含めることができるのは1000項目のみです。 (ビジネスルール)。

これが最大ですが、orderline itemの平均数、5?の平均的なケースも考慮する必要があります。

はい、大きなorderの場合、line itemの追加は遅くなりますが、どれくらいの頻度で発生しますか?

また、リポジトリにline itemカウントをクエリしてこのビジネスルールを保護し、数値1000!をチェックすることはできません。このクエリ+チェック操作は結果整合性があります(注文に999アイテムがある場合、2つのラインアイテムを同時に追加できます)!

1