彼のDDDの本で、Evansは階層化アーキテクチャのアイデアを推進しています。特に、ビジネスロジックはドメインレイヤーに限定し、UI /永続性/その他の問題から分離する必要があります。また、エンティティと値オブジェクトへのアクセスと永続的なストレージを抽象化する手段として、リポジトリパターンを紹介しています。私には次のことがはっきりしていません:
(私は主にDDDの観点に興味があります)
リポジトリとそれらのコード構造への配置は、DDD業界で激しい議論の的となっています。これは好みの問題でもあり、多くの場合、フレームワークとORMの特定の能力に基づいて決定されます。
インフラストラクチャ層での具体的な実装を提供しながらドメイン層で抽象的なリポジトリを使用することを提唱するClean Architectureのような他の設計哲学を考えると、この問題は混乱します。
しかし、さまざまな順列/組み合わせを試した後、私が発見したものと、私のために機能したものは次のとおりです。
あなたの質問に:
リポジトリはどのレイヤーに属しますか:ドメインレイヤー、永続レイヤー、またはその中間の何か?
DDDアプリケーションには3つの異なるレイヤーがあると思います。内部ドメインレイヤー、外部アプリケーションレイヤー、外部ワールド(API/UIを含む)です。
ドメイン層には、集計、エンティティ、値オブジェクト、ドメインサービス、およびドメインイベントが含まれます。これらの要素は相互に依存しているだけであり、外層への依存を積極的に回避します。
アプリケーション層には、アプリケーションサービス、リポジトリ、メッセージブローカー、およびアプリケーションを実際に実現するために必要なその他のものが含まれます。この層では、永続化、承認、pub-sub処理などのほとんどが行われます。アプリケーション層はドメイン層の要素に依存し、それを知っていますが、ドメインモデルにアクセスするときはDDDガイドラインに従います。たとえば、AggregatesおよびDomain Servicesのメソッドのみを呼び出します。
最外層には、APIコントローラー、シリアライザー、認証、ロギングなど、ビジネスロジックやドメインに関係なく、アプリケーションの大部分が含まれます。
エンティティ、値オブジェクト、またはドメインサービスはリポジトリを呼び出すことができますか?
リポジトリは、ストレージテクノロジーから抽象化する必要がありますか(ドメインレイヤーに属している場合に暗示されます)、またはそれらのストレージテクノロジーを活用できますか?
リポジトリはドメイン側に寄りかかっています。つまり、ドメインの観点から意味のあるメソッド(GetAdults()
またはGetMinors()
など)が含まれています。しかし、具体的な実装はいくつかの方法で行うことができます:
リポジトリにビジネスロジックを含めることはできますか?
GetAdults()
の代わりに.filter(age > 21)
と呼ばれるメソッドを使用する方がはるかに優れています。これらの制約を実際に適用しましたか?プロジェクトの品質にどのような影響がありましたか?
アプリケーションサービスでのみリポジトリの使用に制限し、1か所でトランザクションを制御する場合(通常は作業単位パターンを使用)、リポジトリの操作は非常に簡単です。私の過去のプロジェクトでは、ドメインレイヤーにライフサイクルメソッドを散在させるのではなく、すべてのデータベースの相互作用をリポジトリに制限することが非常に有用であることを発見しました。
集約層からライフサイクルメソッド(save
、update
など)を呼び出したところ、ACIDトランザクションを確実に制御することは非常に複雑で難しいことがわかりました。
リポジトリはどのレイヤーに属しますか:ドメインレイヤー、永続レイヤー、またはその中間の何か?
依存関係の逆転を信じるなら*の場合、リポジトリは、アプリケーション層によって定義され、永続層によって実装されるコントラクトです。コントラクト自体は、(署名内のドメインエンティティの外観を介して)ドメインモデルに暗黙的に依存します。リポジトリの実装には、インスタンスの作成を担当するファクトリへの直接の依存関係が含まれることがよくあります。
エンティティ、値オブジェクト、またはドメインサービスはリポジトリを呼び出すことができますか?
理想的には、そうする必要はありません。ドメインモデルは 玉ねぎ の中心にあります。これは、ドメイン(の一部)のメモリ内の純粋な表現です。
facade として機能するドメインサービスがキャッシュデータの読み取り専用ビューを提供する場合があります。しかし、ドメインモデルからリポジトリへの書き込みは通常、範囲外です(依存関係の矢印はその方向を指していません)。
リポジトリはストレージテクノロジーから抽象化されるべきか
理想的には、リポジトリコントラクトは、特定のテクノロジに必ずしも結び付けることなく、アプリケーションに必要な機能を明示的に記述します。
リポジトリにビジネスロジックを含めることはできますか?
理想的な形では、リポジトリはドメインにとらわれないコレクションのファサードです。いいえ。これは、前の質問にいくぶん関連しています。リポジトリの実装に重要なビジネスロジックが含まれている場合、ロジックを見つけたり、ロジックが選択したストレージに与える影響を認識したりすることがはるかに難しくなります。
これらのパターンの動機の実際の部分は、パターンを維持しやすくする試みです。これには、コードベースのどこで重要な概念が実現されるかを推測しやすくすることも含まれます。
これらの制約を実際に適用しましたか?プロジェクトの品質にどのような影響がありましたか?
これはかなり主観的ですが、Evansが説明したように、リポジトリパターンにはあまり喜びを感じていません。問題の一部は、パターンが 名詞の王国 に非常に基づいていることです。もう1つは、2003年頃のJavaのイディオムに結合しているように見えることです。
考え方は正しいです。アプリケーションは、変更可能な状態が管理されている場所の詳細を知る必要はありません。これらの概念の間に正しく境界を置くことができます。同様に、ホストを変更できるように、ドメインモデルはそのアプリケーションホストから独立している必要があります。
物事が故障する傾向があるのはsave
です。必要なデータをオブジェクトからストレージにどのように取得しますか?ドメインモデルにはそれがあり、リポジトリにはそれが必要なため、「純粋な」ドメインモデルに永続的な汚染が発生することになります。汚染は、ORMフック、パブリックメンバー、シリアル化APIなど、さまざまな形で発生します。
エンティティは不変である必要があると仮定しましょう。定義により、リポジトリはエンティティのメモリ内コレクションのエミュレーションであり、一種のマップです
とても近い。 entitiesよりもmessagesの観点から考えると、意味的にはおそらくこれがより適切です。ドメインモデルと永続性の両方で、メッセージから情報を抽出して作業を完了する方法を知る必要があります。 Gary Bernhardtによる Boundaries を参照してください。
または、ビジネスロジックからエンティティを分離することにより、(どこかに)可変エンティティを格納するリポジトリの概念を維持できます。そのアプローチでは、エンティティAPIはドメインモデルによって使用されますが、永続性コンポーネントによって実装されます。これにより、ソリューションの境界であるミュータブルな状態が維持されます。
* 「依存関係の逆転」という用語は、他のすべてのSOLID=原則と同様に不適切に定義されています。インターネット上の記事はすべて、同じ疲れたフレーズをオウムで定義して定義しています。この回答の目的のために、依存関係反転は、「インターフェースによって定義されたコンストラクタパラメータを介して、クラスの具体的な依存関係をクラスに渡すこと」と定義できます。例については、 ここ を参照してください。
したがって、ネットで見つけることができるすべての「集計デザインルール」の話を無視し、本で集計がどのように記述されているかに焦点を合わせる場合、それらは、ある種のグラフを形成するオブジェクトのこれらのバンドルであり、明示的な設計境界と、ルートとして選択された1つのオブジェクト。ルートは、集合体へのインターフェース(ファサード)として機能し、それをカプセル化し、その境界内の一貫性を維持する責任があります。その場合、ドメインモデルは、多かれ少なかれ、トラバーサルによって到達できるそのような多数の集約から構成されます。集約のもう1つの重要な役割は、オブジェクト間の依存関係のWebを単純化することです。そのため、集約関係を戦略的に設計する必要があります。
問題は、どのように初期集計を取得するかです-トラバースを開始する集計ですか?
Evansがリポジトリをどのように定義したかを詳しく見てみると、永続性に重点が置かれていません(ただし、一般的には最終的にはデータベースに行き着きます)。代わりに、それらは初期の「表面レベル」の集計ルートを取得し、あなたはそこから始めて、トラバーサルを介して他の人に到達します。
ブルーブックを引用するには:
「トラバーサルを提供するか、[データベース]検索に依存するかは、設計の決定となり、関連の凝集性に対する検索の分離をトレードオフします。[...]検索と関連の適切な組み合わせにより、設計がわかりやすくなります。 」
つまり、依存関係の構造は、(1)実行パスが入力されると、ユースケース1 アグリゲートが必要な場合は、リポジトリに取得を要求する必要があります。(2)リポジトリは特定の「サーフェスレベル」アグリゲートを認識していますが、コアドメインはリポジトリを認識していません。
それをレイヤーに編成する方法に関しては、多少の変更の余地がありますが、上記の構造がレイヤー間およびレイヤー内の依存関係の方向にいくつかの制約を課していることがわかります。だからあなたの質問に答えます。
- リポジトリはどのレイヤーに属しますか:ドメインレイヤー、永続レイヤー、またはその中間の何か?
DDDによって概念化されたリポジトリは抽象化です-それ自体は永続層ではなく、データベースアクセスをカプセル化します-つまり、再構成要求を永続層にあるデータアクセスゲートウェイに転送します(構成または動的ディスパッチ経由) )。
「グローバルアクセスが必要なオブジェクトのタイプごとに、既知のグローバルインターフェースを介してアクセスを設定します。[リポジトリは実際のストレージとクエリテクノロジーをカプセル化します。実際に直接アクセスが必要なAGGREGATEルートにのみリポジトリを提供します。」
それらはアプリケーション層全体からアクセスできるように意図されているので、それはそれらが常駐するのに自然な場所のように見えますが、実際の実装は層を越えて行きます。カプセル化の理由は、アプリケーションサービスレイヤーのコードをシンプルに保ち、永続性関連のボイラープレートから分離するためです。
(ドメインレイヤーの下にある場合、レイヤードアーキテクチャの原則に違反しているようです。これは、ドメインレイヤーが格納するドメインオブジェクトに依存しているためです)。
リポジトリの抽象化はドメインレイヤーの下にはありませんが、ドメインオブジェクトに依存しています。ドメインモデルの言語を使用して(たとえば、IDを渡すなどして)集計ルートを要求すると、それをデータベースクエリに変換する方法がわかります。データベースはその表現を返し、リポジトリはそれを使用して要求された集約を再構成し、thatを返します。それ以外には、重要なビジネスロジックは含まれておらず、問題固有の(コア)ドメインロジックも含まれていません。ただし、実際には、表現間の複雑でコストのかかる変換を回避する方法で物事を調整したいとします。
この本で説明されているもう1つのことは、コード自体で集計境界を表現する方法を見つけることが望ましいことですが、そうするのは少し難しい場合があります。あなたがウェブ上で見つける最も一般的なものは、Vaughn Vernonによって一般化されたアイデアです。これは、集合体は他の集合体をIDのみを介して参照するべきだと述べています。これは多くの場合にうまく機能し、コード内の集計境界を明確に描くという利点がありますが、IMOは制限が厳しく、データベース中心に考えているため、適用するルールのステータスに昇格したり、デフォルトに設定したりすることさえできます。集約設計になると。したがって、それを唯一の選択肢と考えないでください。別のスキームを設計することもできます。
そのパスをたどると、特定の制限を与えられた集合体を横断する方法を理解する必要があります。 Vaughn Vernon 推奨 「リポジトリまたはドメインサービスを使用して、集約動作を呼び出す前に依存オブジェクトをルックアップする」(および集約内からリポジトリを使用せず、集約が依存しないようにする) 1つ以上のリポジトリ)。
ここには少しトレードオフがあります(すべての設計決定と同様)。集約の内部動作に関する一定量の知識が他の場所にプッシュされ、集約のメソッドのシグネチャはより冗長になります(一部の依存関係は少なくなります)コンストラクタでは表示されないため、明らかです)。
これは実行可能なアプローチですが、エバンスの本で説明されているものとは少し異なることに注意してください-ここでは、リポジトリを通じてどの集約ルートを取得し、トラバーサルによってどのルートを取得するかについて非常に慎重に考えるべきでした。状況-ドメイン、ビジネスプロセス、アクセスパターンなどの理解に基づく.
1ここでDDD用語で重要な2つの層は、アプリケーション層とドメイン層です。アプリケーションレイヤー(「ソフトウェアが行うことになっているジョブを定義し、表現力のあるドメインオブジェクトに問題を解決するよう指示する」)は、クリーンアーキテクチャのアプリケーションビジネスルール(ユースケース)レイヤー(「ユースケース[...]エンティティに、企業全体のビジネスルールを使用して、ユースケースの目標を達成するよう指示します」)。
dddsample によると、リポジトリをドメインレイヤーにインターフェイス( CargoRepository )として、実際の実装( CargoRepositoryHibernate )をインフラストラクチャレイヤーに持つことができます。
レイヤードアーキテクチャの図(DDDブルーブックからも):