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


40

私はドメイン駆動設計に飛び込み、私が出会う概念のいくつかは表面的にはかなり理にかなっていますが、それらについてもっと考えるとき、それは本当に良いアイデアかどうか疑問に思う必要があります。

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

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

私は右の理解集約だ場合、私は一般的にメンバーが含まれますOrderAggregateを返すために、リポジトリのパターンを使用することになりGetAllGetByIDDelete、とSave。いいですね。しかし...

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

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

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

誰も私のためにこれを明確にすることはできますか?

編集:

さらに調査を重ねた結果、ここでの断絶は、純粋なリポジトリパターンが、ほとんどの人がリポジトリと考えているものと異なることだと思います。

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

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

ほとんどの人は、リポジトリを栄光に満ちたデータアクセスオブジェクトと考えているようです。ここでは、必要なデータを取得するメソッドを作成するだけです。これは、ファウラーの「エンタープライズアプリケーションアーキテクチャのパターン」で説明されているような意図ではないようです。

さらに、リポジトリは、主にテストとモック作成を簡単にするため、または永続性をシステムの残りの部分から切り離すために使用される単純な抽象化と考えています。

答えは、これが最初に思っていたよりもはるかに複雑な概念であると思います。


4
「答えは、これが最初に思っていたよりもはるかに複雑な概念だということだと思います。」これは非常に真実です。
クエンティン・スターリン

状況に応じて、データが要求されたときにのみ選択的にデータを取得およびキャッシュする集約ルートオブジェクトのプロキシを作成することがあります
スティーブンA.ロウ

私の提案は、ルート集合体の関連付けに遅延ロードを実装することです。したがって、多くのオブジェクトをロードせずにルートのリストを取得できます。
ジュリアーノ

4
ほぼ6年後、まだ良い質問です。赤い本の章を読んだ後、私は言うだろう:あなたの集計を大きくしすぎないでください。ドメインからいくつかのトップレベルの概念を選択し、DDDを除くすべてを支配するルートと宣言するのは魅力的です。また、上記で説明したような非効率性を軽減します。
Cpt。Senkfuss

ドメインに対して自然で効果的である一方で、集計は可能な限り小さくする必要があります(チャレンジ!)。さらに、リポジトリに非常に具体的なメソッドがあることは完全に問題なく、望ましいことです。
ティモ

回答:


30

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

実際、あなたが尋ねているのは、それを避けるために一連の原則とパターンが確立されているという十分に一般的な質問です。CQRSと呼ばれます


2
@Mystere男:いいえ、それはある必要な最小限のデータを提供します。これは、分離読み取りモデルの大きな目的の1つです。また、いくつかの並行性の問題に前もって取り組むのに役立ちます。CQRSをDDDに適用すると、いくつかの利点があります。
クエンティン・スターリン

2
@Mystere:私がリンクした記事を読んでいると、あなたがそれを見逃すのをとても驚いています。「クエリ(レポート)」というタイトルのセクションを参照してください。「アプリケーションがデータを要求している場合...これはクエリレイヤーへの1回の呼び出しで行う必要があり、その代わりに必要なすべてのデータを含む1つのDTOを取得します...これは、通常、ドメインの動作が実行されているよりも多くの回数データが​​クエリされるため、これを最適化することにより、システムの知覚パフォーマンスが向上します。」
クエンティン・スターリン

4
これが、リポジトリを使用してCQRSシステムのデータを読み取らない理由です。クエリをカプセル化する単純なクラスを作成し(便利で必要な技術を使用して、多くの場合、ADO.Net、Linq2Sql、またはSubSonicが適切です)、手元のタスクに必要な(すべての)データのみを返します。 DDDリポジトリのすべての通常のレイヤーを介してデータをドラッグします。ドメインにコマンドを送信する場合にのみ、リポジトリを使用して集計を取得します。
クエンティン・スターリン

9
「必要のないときに、情報の集合全体を返すことを支持する人がいるとは想像できません。」私はあなたがこの声明で正確に正しいと言っています。必要のない情報の集合全体を取得しないでください。これは、DDDに適用されるCQRSの中核です。クエリに集計は必要ありません。別のメカニズムを使用してデータを取得し、それを一貫して実行します。
クエンティン・スターリン

2
@qes確かに、最善の解決策はクエリ(読み取り)にDDDを使用しないことです:)しかし、コマンド部分、つまりデータの保存または更新にはDDDを使用します。質問があります。DBのデータを更新する必要があるときは、常にエンティティのあるリポジトリを使用しますか?列の小さな値を1つだけ変更する必要があり(何らかのスイッチ)、エンティティ全体をAppレイヤーにロードし、1つの値(プロパティ)を変更してから、エンティティ全体をDBに保存し直しますか?ちょっとやりすぎ?
アンドリュー

8

私は、ドメイン駆動設計でリポジトリパターンを最適に使用する方法に苦労し、今も苦労しています。初めて使用した後、次のプラクティスを思いつきました。

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

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

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

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

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


確かに複雑なテーマです。特に2つの異なる理論を単一の実践に結合する場合、理論を実践に変えることは困難です。
セバスチャンパッテン14

6

GetOrderHeadersメソッドがリポジトリの目的を完全に無効にしているとは思いません。

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

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


おそらく私はここで概念を混乱させていますが、リポジトリパターンの私の理解は、永続性の標準インターフェイスを使用して、永続性をドメインから分離することです。特定の機能にカスタムメソッドを追加する必要がある場合、それは物事を再び結合しているようです。
エリックファンケンブッシュ

1
永続化メカニズムはドメインから分離されますが、永続化されるものではありません。「ここに注文ヘッダーをリストする必要があります」などと言ったら、ドメインでOrderHeaderをモデル化し、リポジトリから取得する方法を提供する必要があります。
エリックキング

また、「永続化のための標準インターフェース」にこだわらないでください。すべての可能なアプリに十分な一般的なリポジトリパターンなどはありません。各アプリには、標準の「GetById」、「Save」などを超える多くのリポジトリメソッドがあります。これらのメソッドは、開始点であり、終了点ではありません。
エリックキング

4

私のDDDの使用は「純粋な」DDDとは見なされない場合がありますが、DBデータストアに対してDDDを使用する次の現実の戦略を採用しました。

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

**集計全体を戻す必要はありません。ただし、さらに必要な場合は、他のサービスやリポジトリではなく、ルートに問い合わせる必要があります。これは遅延ロードであり、貧弱な男性の遅延ロード(適切なリポジトリ/サービスをルートに挿入)で手動で実行するか、これをサポートするORMを使用できます。

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


3

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

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

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

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

リポジトリは、ドメインとデータマッピングレイヤーを仲介し、インメモリドメインオブジェクトコレクションのように機能します。クライアントオブジェクトは、クエリ仕様を宣言的に構築し、満足のためにリポジトリに送信します。(ファウラーのリポジトリページから

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

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


0

これは古い質問ですが、別の答えになったようです。

リポジトリを作成すると、通常、キャッシュされたクエリがラップされます。

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

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

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

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

  1. データベースにクエリを実行して、リストを作成するために必要なものを正確に取得し、再度クエリを実行して、詳細ページに表示する必要がある個々の詳細を取得できます。

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

実際には、それをどのように実装するかはまさにそれであり、実装の詳細です。私の最大のユーザーが10件の注文を持っている場合、おそらくオプション2を使用します。10,000件の注文を話している場合は、オプション1が必要です。上記の場合と他の多くの場合の両方で、その実装の詳細をリポジトリに隠したいです。

先月に注文リストページで注文(集計データ)に費やした金額をユーザーに伝えるチケットを取得した場合、SQLでそれを計算するロジックを記述し、さらに別のラウンドトリップを作成しますDBまたはサーバーRAMに既に存在するデータを使用して計算しますか?

私の経験では、ドメイン集合体には大きな利点があります。

  • これらは、実際に機能する巨大なパーツコードの再利用です。
  • インフラストラクチャレイヤーをドリルスルーしてSQLサーバーに実行させる代わりに、ビジネスロジックをコアレイヤーで維持することにより、コードを簡素化します。
  • また、簡単にキャッシュできるため、実行する必要のあるクエリの数を減らすことで、応答時間を大幅に短縮できます。
  • 私が書いているSQLは、多くの場合、すべてを要求し、サーバー側を計算しているだけなので、多くの場合、ずっと保守しやすいです。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.