Entities/AR's 自体から Services 、Vaughn Vernonのヒントを守りながら、次のことを行います。
直接オブジェクト参照(または「ポインター」)を保持することではなく、グローバルに一意のIDによってのみ外部集約への参照を優先してください。
簡単な検索システムを例にとってみましょう。この検索システムを使用すると、疑わしい人物のリストに対してスタンドアロンの検索顧客を実行できます。
バッチ検索を実行することもできます。これは顧客のリストに対する検索の単なるコレクションです。
上記のようにモデル化しました。
本質的に
どちらも集合ルートです。 Searchはスタンドアロンで実行できますが、Batch Searchの一部として実行することもできます。その場合、作成されたBatch Searchには-が含まれます検索。
これをコーディングすることはかなり簡単です
class BatchSearch {
constructor(customers) {
this.searches = []
this.customers = customers
}
run() {
for (customer of this.customers) {
const search = new Search(customer)
search.run()
this.searches.Push(search)
}
this.markAsCompleted()
}
...
}
ただし、Vaughn Vernonは、Aggregate Roots間の直接参照を保持することは悪い設計であると述べています。
から、 効果的な集計デザインパートII:集計を連携させる :
直接オブジェクト参照(または「ポインター」)を保持するのではなく、グローバルに一意のIDによってのみ外部集約への参照を優先します.....集約動作を呼び出す前に、リポジトリまたはドメインサービス(7)を使用して依存オブジェクトを検索します
私が理解しているように、彼は次のようにサービス内のアグリゲート間の相互作用を移動することを提唱しています。
class BatchSearchService {
constructor() {
}
createBatchSearch(customers) {
let searches = []
const batchSearch = new BatchSearch()
for (customer of customers) {
const search = new Search(customer)
// We link this Search with this Batch Search by ID only, here
search.setBatchSearchId(batchSearch.getId())
search.run()
searches.Push(search)
}
batchSearch.markAsCompleted()
batchSearchRepo.save(batchSearch)
searchRepo.save(searches)
}
}
この推奨事項は常に 貧血ドメインモデル につながるのではないですか?
AFAIK OOPは基本的にクラス内のデータと操作の結合ですが、このシナリオで私が理解していることから、操作は Batch Search Class 代わりにバッチ検索サービスに、バッチ検索クラスはデータのみを保持する
Vaughn Vernonの推奨は、従うべき最高の戦術ルールの1つです。別の集約ルートへの参照を保持する必要がある場合、集約の境界を確認する必要があります。これはおそらくそれらが間違っているためです。
この場合ビジネスが貧弱であるためモデルは貧弱です、少なくとも提示したものから保護する必要のある不変量はありません。いずれの場合でも、aggregates
はアーキテクチャの書き込み/コマンド側で使用する必要があります。私がこれを指定しているのは、ほとんどすべてのドメインで、「検索」は読み取り/クエリ側の操作ですが、「検索」が何らかの状態の変化を意味する特別なドメインであると思います。
ここに2つの集合根があるとは思いません。集約ルートはエンティティです。これは、ドメインレベルで「継続性とアイデンティティ」を持っていることを意味します。説明、コメント、コードサンプルから、Search
タイプのみが実際にエンティティであるように見えます。 BatchSearch
自体はそのようなIDを欠いているようです(少なくとも、そのようなIDはコードの最初のバージョンでは表示されません)。これにより、(DDD)サービスになる可能性があります。特に、外部から見える状態がない場合。使用方法によっては、値オブジェクトとして扱うこともできます。さて、それがサービスである場合、これは機能をいくつかのユーティリティメソッドに移動する必要があることを意味しないことに注意してください。サービスはオブジェクトとして実装できます(ポリモーフィックである、依存関係があるなど)。
Vaughn Vernonのポイントは、集合体の状態をロードおよび変更することです。集約は、関連するエンティティと値オブジェクトのグループであり、外部から見えるentity(集約ルート-基本的に Facade )、これは(1)走査のルールを定義し、複雑さを隠すこと、および(2)変更が加えられたときに全体としての集合体の一貫性についていくつかの保証を提供するという2つの目的を果たします。リンク先の記事では、関連する2つの集約を1つのアトミック操作で変更しないでください。これは特定のデザインとパフォーマンスに影響を与えるためです(アトミックな一貫性が目標であると想定しています)。
したがって、この状況を回避するために、ドメインIDで他のアグリゲート(これもエンティティです)を参照することをお勧めします。これは問題のあるシナリオを完全に防ぐわけではありませんが、他の集約へのナビゲーションを許可しながら、実現をより困難にします。また、2つのアグリゲートが個別に読み込まれるという意味で、メモリフットプリントが削減されるという利点もあります。
モデルナビゲーションセクションでは、遅延ロードの形式を使用して集約ルート間のナビゲーションをサポートする方法について説明し、次のように「集約動作を呼び出す前にリポジトリまたはドメインサービスを使用して依存オブジェクトを検索する」ことを推奨しています。アグリゲート内からリポジトリを介して依存関係を検索するのとは対照的です。これは、依存関係の逆転の原理の単なるアプリケーションです(この「リポジトリまたはドメインサービス」は、依存関係の注入を手動で実行するために使用されます)。
彼はさらに、このアプローチが機能しないことが判明した場合は、結果整合性のためにシステムを設計することを検討する必要があると述べています(つまり、システムは、1つのアグリゲートの正常な更新が関連アグリゲートがまた、それに応じて変更され、その時点で有効な状態にあります)、そのようなシステムを構築するためのイベントベースのアプローチについて説明します。
さて、問題は、これらのいずれかがあなたのBatchSearch
クラスに当てはまりますか?非一時的なドメインレベルのIDがありますか?一連のドメインエンティティ(Search
インスタンス)を作成して返すサービスとしてモデル化した方がよいですか?また、記事自体は、ルールを破ることを決定する場合があることを指摘し、そうするためのいくつかの理由をリストしています。これも考慮すべきことです。
モデルを統合する1つの方法は、検索を集約ルートとして使用し、呼び出しモデルを集約の一部として使用することです。
これは、すでにモデル化した方法と同じですが、検索とバッチ検索を2つの異なる集約モデルとして考える必要はなくなります。
ストア検索状態をバッチ処理したかどうかを表すフラグを持つリソースとして。
class Search {
List<Invocation> invocationList;
boolean: batched;
// you can check size of the list to determine whether it's batched or not - this simplifies the model further
}
class Invocation {
List<SearchParameters> searchParameterList;
..
//add more fields as required
..
}
ユースケースを設計し、各呼び出しを異なる数の呼び出しでSearchとして扱うようにモデル化します(ユースケースによっては、「呼び出し」の名前が異なる場合があります)。
したがって、モデルは特定のコンテキストで集約ルートの境界内にとどまります。
それが役に立てば幸い。見落とされた設計上の懸念を説明する必要があるかどうかをお知らせください。