私はDDDブルーブックを読んでいますが、現時点ではまだはっきりしないことが1つあります。それは、多数の子エンティティが含まれているAggregatesに対して単純な操作を効率的に実行する方法です。
Line Item
_ sのあるOrder
、(Aggregate Root)があることを考慮してください。Order
には、1000個の_Line Item
_ sのみを含めることができます。 (ビジネスルール)。_Line Item
_をOrder
に追加するには、次のようにする必要があります。
Order
をallで再構成します。リポジトリの_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
に存在する必要があることを要求するビジネスルールをチェックできません。
また、遅延読み込みの実装は、通常、プロパティアクセサーとリポジトリを結び付けるため、アンチパターンとして私を攻撃します。
DDDを使用すると、ドメインモデルを改善するためにパフォーマンスが低下します。そして、これはその妥協が行われるかもしれない領域です。
あなたの問題を見て、LineItem
がOrder
集合体の一部である必要があるかどうか質問します。集計のポイントは、複数の複雑な操作が行われるエンティティをグループ化することです。内部エンティティと10の操作を持つ集計があるとします。 1つのケースでは、9つの操作がエンティティで機能します。そのような場合、エンティティが常に読み込まれているとは限らない場合、状況は大幅に複雑になります。一方、これらのエンティティが必要な操作が1つだけの場合は、それらのエンティティを集合体の一部にする必要があるかどうかを質問します。操作が複雑な場合、ケースは比較的単純です。アイテムのカウントを取ることは、DB側で実行できる簡単な操作です。しかし、制限が価格ではなく、数ではなかったと想像してみてください。この場合、複雑な「価格計算」ロジックを実行する必要があります。これには、おそらくすべてのデータを持つすべての注文アイテムが必要になります。つまり、広告申込情報を読み込まないことはできません。
あなたの問題は、実際のItem
オブジェクトをOrder
集合体に保持する必要があると考えていることだと思います。それは間違いです。これを行うと、メモリのフットプリントは非常に狂ってしまい、パフォーマンスはひどいものになります。また、1つの操作(コマンド)は1つの集合体にのみ影響を与えるため、これを行うべきではありません。これにより、コードの一貫性と簡潔さが同時に向上します。
より高いパフォーマンスと一貫性を維持するために、実際にはLineItem
はOrder
とItem
の両方の識別子への参照のみを保持する非常に単純なオブジェクトです。
_final class LineItem {
private final OrderId orderId;
private final ItemId itemId;
public LineItem(OrderId orderId, ItemId itemId) {
this.orderId = orderId;
this.itemId = itemId;
}
}
_
Item
をOrder
集合体に追加すると、次のようにモデル化されます。
_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()));
}
}
_
これからデータをどのように読み取るのか疑問に思っている場合、答えは、あなたがする必要はなく、通常はそうしないことです。集合体はサイズが大きくなる可能性がありますが、これはビジネスルールの一貫性と可視性を維持するために有利です。読み取りとパフォーマンスのためには、データベースと直接通信する別のレイヤーが必要であり、完全に集約を回避します。これを行うことができるのは、データベース内のすべてのデータが有効であることがわかっているためです。すべての変更は、ルールを保護する集約を通じて行われるためです。
この問題を解決する別のオプションは、他の人がすでに示唆しているように、OrderRepository
にGetItemsCount(OrderId)
を含めることです。それでも、あなたの主な問題は、集計を誤って参照することを考えることだと思います。
集計モデリングの非常に優れたソースを検討している場合は、必ずVaugh Vernon氏による Effective Aggregate Design を参照してください。
GetOrderItemsCount()
と呼ばれるメソッドをリポジトリに追加し、それに興味のある注文のIDを渡し、現在注文にあるアイテムの数を返します。これはビジネスルールを満たし、必要に応じて最適化できます。
新しいアイテムを追加するために既存のアイテムをすべてロードする必要はありません。特に、現在のカウントがすでにわかっており、ビジネスルールに対してチェックしている場合は特にそうです。
遅延読み込み?遅延読み込みとは何ですか?
DDDでは、コマンドを適用する前に、リポジトリからAggregate全体をロードする必要があります。完全にロードする必要がないと思う場合は、Aggregate境界が間違っている可能性があります。Aggregateが大きすぎる可能性があります。アグリゲートはトランザクションの境界であることを覚えておいてください。アグリゲート内で発生するすべてのことは同時更新から安全であり、操作には強い整合性があります。これはsafe-nessはいい方法ですが、コストがかかるため、実際のビジネスに従って実行する必要があります。
また、遅延読み込みの実装は、通常、プロパティアクセサーとリポジトリを結び付けるため、アンチパターンとして私を攻撃します。
はい、Aggregate設計のコンテキストでは、遅延読み込みはアンチパターンです。これは、集計境界が間違っている(最も可能性が高い)ことを意味します。
各注文に含めることができるのは1000項目のみです。 (ビジネスルール)。
これが最大ですが、order
がline item
の平均数、5
?の平均的なケースも考慮する必要があります。
はい、大きなorder
の場合、line item
の追加は遅くなりますが、どれくらいの頻度で発生しますか?
また、リポジトリにline item
カウントをクエリしてこのビジネスルールを保護し、数値1000
!をチェックすることはできません。このクエリ+チェック操作は結果整合性があります(注文に999アイテムがある場合、2つのラインアイテムを同時に追加できます)!