web-dev-qa-db-ja.com

DDD:本当にすべてのオブジェクトを集約でロードする必要がありますか? (パフォーマンスの懸念)

DDDでは、リポジトリはアグリゲート全体をロードします。すべてをロードするか、まったくロードしません。これは、遅延読み込みを回避する必要があることも意味します。

私の懸念はパフォーマンスの面です。これにより、何千ものオブジェクトがメモリに読み込まれる場合はどうなりますか?たとえば、Customerの集計は1万Ordersで返されます。

このような場合、アグリゲートを再設計して再考する必要があるということでしょうか。 DDDはこの問題に関する提案を提供しますか?

24
user11081980

これを見てください Effective Aggregate Design Vernonからの3つの記事のシリーズ。大規模なクラスターの集合体ではなく、いつ、どのように小さな集合体を設計できるかを理解するのに非常に役立つことがわかりました。

[〜#〜]編集[〜#〜]

私の以前の答えを改善するためにいくつかの例を挙げたいと思います。それらについてのあなたの考えを自由に共有してください。

まず、集合体についての簡単な定義(Scott MilletによるPatterns、Principles and Practices of Domain Driven Designbookから引用)

エンティティと値オブジェクトは連携して、ドメインモデル内の不変条件を満たす複雑な関係を形成します。オブジェクトの相互接続された大規模な関連付けを処理する場合、ドメインオブジェクトに対してアクションを実行するときに、一貫性と同時実行性を確保することが難しいことがよくあります。ドメイン駆動設計には、一貫性を確保し、オブジェクトグラフのトランザクション同時実行境界を定義するための集約パターンがあります。大規模なモデルは不変条件によって分割され、概念全体として扱われるエンティティと値オブジェクトの集合体にグループ化されます。

実際の定義を確認するために例を見てみましょう。

簡単な例

最初の例は、集約ルートを定義すると、ドメインオブジェクトに対してアクションを実行するときに一貫性を確保するのにどのように役立つかを示しています。

次のビジネスルールを考えると:

落札したオークションの入札は、常にオークションが終了する前に行う必要があります。オークション終了後に落札した場合、不変条件が破られ、モデルがドメインルールを正しく適用できなかったため、ドメインは無効な状態になります。

ここには、オークションと入札で構成されるアグリゲートがあり、オークションはアグリゲートルートです。

入札も分離された集約ルートであると言えば、BidsRepositoryがあり、簡単に次のことができます。

var newBid = new Bid(money);
BidsRepository->save(auctionId, newBid);

そして、定義されたビジネスルールを通過せずに入札を保存していました。ただし、次のようなことを行う必要があるため、オークションを唯一の集約ルートとして設計を実施します。

var newBid = new Bid(money);
auction.placeBid(newBid);
auctionRepository.save(auction);

したがって、メソッドplaceBid内で不変条件をチェックでき、新しい入札を配置したい場合は誰もそれをスキップできません。

ここで、入札の状態がオークションの状態に依存することはかなり明らかです。

複雑な例

顧客に関連付けられている注文の例に戻ると、顧客とそのすべての注文で構成される巨大な集計を定義する不変条件はないようです。識別子参照を通じて、両方のエンティティ間の関係を維持できます。これにより、顧客を取得するときにすべての注文をロードすることを回避し、同時実行の問題を軽減します。

しかし、今ビジネスが次の不変条件を定義するとします。

私たちは、顧客が製品を購入するためにお金を請求できるように、顧客にポケットを提供したいと考えています。したがって、顧客が製品を購入したい場合、それを行うのに十分なお金が必要です。

そうは言っても、ポケットはカスタマーアグリゲートルート内のVOです。ルールをチェックせずに新しい注文を保存できるため、顧客用と注文用の2つの分離された集約ルートを持つことは新しい不変条件を満たすのに最適ではないようです。お客様をルートと見なすことを余儀なくされているようです。これは、パフォーマンス、スケーラビリティ、同時実行性の問題などに影響します。

解決?結果整合性。顧客に製品の購入を許可した場合はどうなりますか?つまり、注文の集計ルートがあるため、注文を作成して保存します。

var newOrder = new Order(customerId, ...);
orderRepository.save(newOrder);

注文が作成されたときにイベントを公開し、顧客に十分な資金があるかどうかを非同期で確認します。

class OrderWasCreatedListener:
    var customer = customerRepository.findOfId(event.customerId);
    var order = orderRepository.findOfId(event.orderId);
    customer.placeOrder(order); //Check business rules
    customerRepository.save(customer);

すべてが良ければ、最初は要求ごとに1つの集約ルートのみを変更するように設計を維持しながら、不変条件を満たしています。それ以外の場合は、資金不足の問題についてお客様にメールでお知らせします。彼女が現在の予算で購入できる代替オプションをメールに追加することで、それを前進させることができます。また、ポケットに請求するように勧めることもできます。

UIは、顧客が十分なお金を払わずに支払うことを回避するのに役立ちますが、UIを盲目的に信頼することはできません。

両方の例がお役に立てば幸いです。公開されたシナリオに対してより良い解決策が見つかった場合はお知らせください:-)

34
mgonzalezbaile

このような場合、アグリゲートを再設計して再考する必要があるということでしょうか。

ほぼ確実に。

集約設計の推進力は構造ではなく、動作です。 「ユーザーが何千もの注文をしている」ことは気にしません。私たちが気にするのは、変更を処理しようとするときにどの状態をチェックする必要があるか、つまり、変更が有効かどうかを知るためにどのデータをロードする必要があるかです。

通常、注文の変更はシステム内の他の注文の状態に依存しない(または依存すべきではない)ことに気付くでしょう。これは、2つの異なる注文が同じ集計の一部であってはならないことを示しています。

16
VoiceOfUnreason