web-dev-qa-db-ja.com

DDDアグリゲートはWebアプリケーションで本当に良いアイデアですか?

私はドメインドリブンデザインに飛び込んでおり、出会っているいくつかの概念は表面上は非常に理にかなっていますが、それらについてさらに考えるとき、それが本当に良いアイデアかどうか疑問に思わなければなりません。

たとえば、集合体の概念は理にかなっています。ドメインモデル全体を扱う必要がないように、所有権の小さなドメインを作成します。

しかし、これをWebアプリのコンテキストで考えると、データベースにアクセスして、データの小さなサブセットを取り戻すことがよくあります。たとえば、ページには注文数のみが表示され、クリックして注文を開いてその注文IDを確認するためのリンクが表示されます。

集約を正しく理解している場合、通常はリポジトリパターンを使用して、メンバーGetAllGetByIDDelete、およびSave。いいですね。だが...

GetAllを呼び出してすべての注文を一覧表示すると、このパターンでは、集計情報の完全なリスト、完全な注文、注文明細などを返す必要があるように見えます。その情報の小さなサブセットのみが必要な場合(ヘッダー情報のみ)。

何か不足していますか?または、ここで使用する最適化のレベルはありますか?あなたがそれを必要としないとき、誰かが情報の全体を返すことを主張することを想像することはできません。

確かに、リポジトリにGetOrderHeadersのようなメソッドを作成することもできますが、それはそもそもリポジトリのようなパターンを使用する目的に反するようです。

誰かがこれを私のために明確にできますか?

編集:

さらに多くの調査を行った結果、ここでの違いは、純粋なリポジトリパターンが、ほとんどの人がリポジトリと考えるものとは異なるということです。

Fowlerは、リポジトリをコレクションセマンティクスを使用するデータストアとして定義し、通常はメモリ内に保持されます。これは、オブジェクトグラフ全体を作成することを意味します。

エバンスはリポジトリを変更して集約ルートを含めるため、リポジトリは切断され、集約内のオブジェクトのみがサポートされます。

ほとんどの人は、リポジトリを栄光のデータアクセスオブジェクトと考えているようです。ここでは、必要なデータを取得するメソッドを作成するだけです。これは、Fowler's Patterns of Enterprise Application Architectureで説明されているようには意図されていないようです。

さらに、リポジトリを、主にテストとモックを容易にするため、または永続性をシステムの他の部分から切り離すために使用される単純な抽象概念と考える人もいます。

その答えは、私が最初に思ったよりもずっと複雑な概念だと思います。

43

クエリにドメインモデルと集計を使用しないでください。

実際、あなたが求めているのは、それを回避するために一連の原則とパターンが確立されているという十分によくある質問です。それは [〜#〜] cqrs [〜#〜] と呼ばれます。

31
quentin-starin

ドメインドリブンデザインでリポジトリパターンを最適に使用する方法に苦労し、現在も苦労しています。初めて使用した後、私は次の方法を思いつきました。

  1. リポジトリはシンプルでなければなりません。ドメインオブジェクトの格納と取得のみを担当します。他のすべてのロジックは、ファクトリやドメインサービスなどの他のオブジェクトにある必要があります。

  2. リポジトリは、まるで集約ルートのメモリ内コレクションであるかのように、コレクションのように動作します。

  3. リポジトリは汎用のDAOではなく、各リポジトリには独自の狭いインターフェースがあります。多くの場合、リポジトリには、ドメインの観点からコレクションを検索できる特定のFinderメソッドがあります(たとえば、ユーザーXのすべての未処理注文を取得できます)。リポジトリ自体は、汎用DAOを使用して実装できます。

  4. Finderメソッドは、集計ルートのみを返すのが理想的です。それが非効率的である場合は、必要なものだけを含むのではなく、読み取り専用の値オブジェクトも返す可能性があります(ただし、これらの値オブジェクトもドメインの観点から表現できる場合はプラスになります)。最後の手段として、リポジトリを使用して、集約ルートのサブセットまたはサブセットのコレクションを返すこともできます。

  5. このような選択は、使用するテクノロジーを使用してドメインモデルを最も効率的に表現する方法を見つける必要があるため、使用するテクノロジーによって異なります。

8
Kdeveloper

あなたのGetOrderHeadersメソッドがリポジトリの目的に反することはないと思います。

DDDは、(特に)集約ルートを介して必要なものを確実に取得すること(たとえば、OrderDetailsRepositoryを持たないこと)に関係していますが、言及している方法に制限されません。

OrderHeaderがドメインコンセプトである場合、それをドメインコンセプトとして定義し、それらを取得するための適切なリポジトリメソッドを用意する必要があります。その際、正しい集約ルートを通過していることを確認してください。

6
Eric King

私のDDDの使用は「純粋な」DDDとは見なされないかもしれませんが、DBデータストアに対してDDDを使用して、次の実際の戦略を採用しました。

  • 集約ルートには関連するリポジトリがあります
  • 関連付けられたリポジトリは、その集約ルートによってのみ使用されます(公開されていません)
  • リポジトリにはクエリ呼び出しを含めることができます(例:GetAllActiveOrders、GetOrderItemsForOrder)
  • サービスは、リポジトリのパブリックサブセットと他の非粗操作(例:ある銀行口座から別の銀行口座への送金、LoadById、検索/検索、CreateEntityなど)を公開します。
  • ルート->サービス->リポジトリスタックを使用します。 DDDサービスは、エンティティがそれ自体に応答できない場合(LoadById、TransferMoneyFromAccountToAccountなど)にのみ使用されることを前提としていますが、実際には、 rootはこれら自身を「応​​答/実行」できる必要があります。エンティティに別の集約ルートサービスへのアクセス権を与えることには何の問題もないことに注意してください。ただし、サービス(GetOrderItemsForOrder)には含めず、リポジトリに含めて、集約ルートがそれを利用できるようにすることを忘れないでください。サービスは、リポジトリのように開いているクエリを公開しないでください。
  • 私は通常、リポジトリを(インターフェースを介して)ドメインモデルで抽象的に定義し、個別の具体的な実装を提供します。ドメインモデルでサービスを完全に定義し、その使用のために具体的なリポジトリに注入します。

**アグリゲート全体を戻す必要はありません。ただし、さらに必要な場合は、他のサービスやリポジトリではなく、ルートを尋ねる必要があります。これは遅延読み込みであり、貧弱な人の遅延読み込み(適切なリポジトリ/サービスをルートに挿入)で手動で実行するか、これをサポートするORMを使用して実行できます。

あなたの例では、別の呼び出しで詳細をロードしたい場合、おそらく注文ヘッダーのみをもたらすリポジトリ呼び出しを提供します。 「OrderHeader」を使用することで、ドメインに追加の概念が実際に導入されることに注意してください。

4
Mike Rowley

ドメインモデルには、ビジネスロジックが最も純粋な形式で含まれています。ビジネスオペレーションをサポートするすべての関係とオペレーション。概念マップから欠落しているのは アプリケーションサービスレイヤー のアイデアです。サービスレイヤーはドメインモデルをラップし、ビジネスドメインの簡略化されたビュー(可能であれば予測)を提供します。サービスモデルを使用するアプリケーションに直接影響を与えることなく、必要に応じて変更するドメインモデル。

もっと遠く行く。集合体の考え方は、集合体の一貫性を維持する責任がある1つのオブジェクト、集合体ルートがあるということです。あなたの例では、注文はその注文ラインを操作する責任があります。

たとえば、サービスレイヤーは、GetOrdersForCustomerのような操作を公開し、注文の概要リストを表示するために必要なものだけを返します(OrderHeadersと呼ぶので)。

最後に、Repositoryパターンは単なるコレクションではなく、宣言型クエリも可能です。 C#では、LINQを Query Object として使用できます。または、他のほとんどのO/RMがQuery Object仕様も提供しています。

リポジトリは、ドメインレイヤとデータマッピングレイヤの間を仲介し、メモリ内のドメインオブジェクトコレクションのように機能します。クライアントオブジェクトはクエリ仕様を宣言的に構築し、満足できるようにそれらをリポジトリに送信します。 ( Fowler's Repositoryページ から)

リポジトリに対してクエリを作成できることを考えると、一般的なクエリを処理する便利なメソッドを提供することも理にかなっています。つまり注文のヘッダーだけが必要な場合は、ヘッダーのみを返すクエリを作成して、リポジトリの便利なメソッドからそれを公開できます。

これが物事を明確にするのに役立つことを願っています。

3
Michael Brown

これは古い質問であることはわかっていますが、別の答えになっているようです。

リポジトリを作成すると、通常、いくつかのcachedクエリがラップされます。

Fowlerは、リポジトリをコレクションセマンティクスを使用するデータストアとして定義し、通常はメモリ内に保持されます。これは、オブジェクトグラフ全体を作成することを意味します。

これらのリポジトリをサーバーのRAMに保持します。オブジェクトをデータベースに渡すだけではありません!

注文を一覧表示するページがあるWebアプリケーションで、クリックして詳細を表示できる場合、注文一覧ページに注文の詳細(ID、名前、金額、日付)が必要になる可能性があります。ユーザーが見たいものを決定するのに役立ちます。

この時点で2つのオプションがあります。

  1. データベースにクエリを実行し、リストを作成するために必要なものを正確にプルバックしてから、もう一度クエリを実行して、詳細ページに表示する必要がある個々の詳細をプルすることができます。

  2. すべての情報を取得してキャッシュする1つのクエリを作成できます。次のページのリクエストでは、データベースではなくサーバーRAMから読み取ります。彼のユーザーが反撃したり次のページを選択したりしても、データベースへのアクセスはゼロのままです。

実際には、それを実装する方法はそれだけであり、実装の詳細です。私の最大のユーザーが10の注文を持っている場合、おそらくオプション2で行きたいと思います。10,000の注文を話している場合は、オプション1が必要です。上記の両方のケースと他の多くのケースで、リポジトリーにその実装の詳細を非表示にしてもらいます。

注文一覧ページの先月に注文に費やした金額(集計データ)をユーザーに知らせるチケットを受け取ったら、それをSQLで計算してDBへのもう1つの往復を行うロジックを作成するのか、それともサーバーのRAMに既にあるデータを使用して計算するのか?

私の経験では、ドメイン集約は大きなメリットをもたらします。

  • それらは、実際に機能する非常に大きなコードの再利用です。
  • インフラストラクチャレイヤーをドリルスルーしてSQLサーバーに実行させるのではなく、コアレイヤーでビジネスロジックを正しく維持することでコードを簡素化します。
  • また、簡単にキャッシュできるため、必要なクエリの数を減らすことで、応答時間を大幅に短縮できます。
  • 私が書いているSQLは、すべてを要求してサーバー側を計算しているだけなので、多くの場合、はるかに保守しやすくなっています。
0
WhiteleyJ