Fowler( here )によると、リポジトリは「ドメインとデータマッピングレイヤーの間を仲介し、メモリ内のドメインオブジェクトコレクションのように機能します」。したがって、たとえば、私のCourier Serviceアプリケーションでは、新しいrunが送信されると、アプリケーションサービスは新しいRun集約ルートオブジェクトを作成し、リクエストからの値を設定してから、作業ユニットを呼び出して保存する前に、それをRunRepositoryに追加しますデータベースへの変更。ユーザーが現在の実行のリストを表示したい場合は、同じリポジトリをクエリして、情報を表す非正規化DTOを返します。
ただし、CQRSを見ると、クエリは同じリポジトリにヒットしません。代わりに、それはおそらくデータストアに直接向かい、常に非正規化されます。そして、私のコマンド側は、NewRunコマンドとハンドラーに進化し、NewRunドメインオブジェクトを作成してデータを設定し、情報をデータストアに永続化します。
したがって、最初の質問は、ドメインオブジェクトのメモリ内コレクション(必要に応じてキャッシュ)を維持していない場合、リポジトリがCQRSモデルにどこに適合するのかということです
アプリケーションサービスに送信された情報に、ドメインオブジェクトを構築するためにサービスが解決する必要がある一連のID値のみが含まれている場合を考えてみます。たとえば、リクエストには実行に割り当てられたクーリエのID#が含まれます。サービスは、ID値に基づいて実際のCourierオブジェクトを検索し、AssignCourierメソッド(クーリエを検証し、その他のビジネスロジックを実行する)を使用してオブジェクトをNewRunに割り当てる必要があります。
もう1つの質問は、クエリの分離とリポジトリの不在の可能性がある場合、アプリケーションサービスはどのようにして検索を実行してCourierドメインオブジェクトを見つけるかということです
[〜#〜]更新[〜#〜]
デニスのコメントの後のいくつかの追加の読書と考えに基づいて、私は私の質問を言い換えます。
CQRSは、データアクセスおよびデータストレージメカニズムの単なるファサードであるリポジトリを推奨しているようです。それらはコレクションの「外観」を提供しますが(Fowlerが説明するように)、エンティティをメモリ内で管理していません(Dennisが指摘したように)。これは、リポジトリでのすべての操作がパススルーであることを意味しますね。
作業ユニットはこのアプローチにどのように適合しますか?通常、UoWはリポジトリに加えられた変更をコミットするために使用されます(そうですか?)が、リポジトリがエンティティをメモリ内に維持していない場合、UoWにはどのような役割がありますか?
「書き込み」操作に関して、コマンドハンドラーは同じリポジトリ、別のリポジトリ、またはリポジトリではなくUoWへの参照を持っていますか?
アプリケーションの状態を表すためにコマンド側で単純なキー値ストアを維持するCQRSシステム、および単にメッセージを関連付ける(ある種の佐賀を使用して)代わりにクエリストアを使用してアプリケーションの状態を表すCQRSシステムについて読みました。どちらの方法でも、これらのアプローチに関連する永続化テクノロジが存在することは間違いありませんが、これらの場合のリポジトリパターンは、その上にある不必要な抽象化です。
私のCQRSでの経験は、これまでのイベントソーシングでしかありませんでした。そこでは、過去のイベントを再生して、ビジネスロジックと不変式をカプセル化して適用する集計を再構築しました。この場合、リポジトリパターンは、これらの集計を取得するためのより簡単な方法を提供できる、おなじみの抽象化です。
クエリ側に関しては、できるだけデータストアに近づけることをお勧めします。つまり、UI(何であれ)とデータストアの間のリポジトリ、サービス、ファサードなどは避けます。
これらのアプローチの使用例を確認すると役立つ場合があります。多分次のプロジェクトを見てください:
NESの場合、リポジトリは、作業ユニットに直接、または作業ユニットからアグリゲートを追加および読み取るための使い慣れたインターフェースを提供するだけです。
役立つかもしれないいくつかのリンク:
これがどれほど正統的であるかはわかりませんが、現在のプロジェクトでは、集約エンティティルートのリポジトリがあります。このリポジトリには、GetとApplyEventsの2つのメソッドしかありません。
すべてのイベントは、そのタイプに共通のインターフェースを実装しています-注文にはOrderEventsなどがあります。私は個人的に各イベントのビジネスロジックをポリモーフィックメソッドに入れているので、新しいタイプのイベントの追加は非常に簡単になります。
Getの場合、リポジトリはイベントストアに移動し、そのタイプのスコープ内のすべてのイベントを取得します(たとえば、単一のストアの場所の注文)。次に、イベントの再生を行って、指定されたすべてのイベントについてエンティティの現在の状態に到達します。スナップショットからも機能するため、ロードするたびにすべてのイベントを再作成する必要はありません。また、一般的なイベントリポジトリを使用して、イベントの格納方法を抽象化し、仕様に基づいてそれらを取得することもできます。
ApplyEventsはイベントのリストを受け取り、これらに基づいてエンティティの状態を変更して返します。エンティティを変更するだけでなく、エンティティを再作成するオプションをリポジトリに与えていることに注意してください。これはプログラミングの関数型でうまく機能しますが、C#またはJavaでオブジェクトの等価性(obj1 == obj2)を回避するのが最善であることを意味します。とにかく、EntitiesではなくValueObjectsだけが同等であるべきだと主張します。
実際の動作は次のとおりです(C#)。注文があり、アイテムを追加したいと思います。 currentOrder.Itemsは空のリストを返します。それから私はします
Assert.IsFalse(newEvent.Items.Any())
IOrderEvent newEvent = eventFactory.CreateOrderItemEvent(myItemID);
currentOrder = orderRepository.ApplyEvents(currentOrder, newEvent);
Assert.IsTrue(newEvent.Items.Any())
CurrentOrder.Itemsに1つのエントリがあることを確認します。
ここでの欠点は、エンティティにビジネスロジックを持たせるのではなく、すべての処理がイベントを通じて行われることです。ただし、私の場合、ほとんどすべてのオブジェクトがシリアル化可能(基本的にPOCO)で複数のシステムで動作する必要があるため、これは実際にうまく機能します。