Eric EvansやVaughn Vernonの本など、ドメイン駆動設計についてたくさん読んだことがあります。したがって、Aggregate Root、Entity、およびValue Objectの概念に精通しています。
しかし、ドメインドリブンデザインアプローチを使用していくつかのドメインモデルをモデリングしているときに、これまでにない疑問が生じました。集約ルートの状態を、子エンティティも含む値オブジェクトとして完全にモデル化できることに気付きました。まず、「通常の」アプローチを紹介しましょう。
class EntityDataVO {}
class Entity {
String id;
EntityDataVO data;
Entity(String id, EntityDataVO data) {
this.id = id;
this.data = data;
}
void update(EntityDataVO data) {
this.data = data;
}
}
class AggregateRoot1 {
private Map<String, Entity> entities = new HashMap<>();
void addEntity(String id, EntityDataVO data) {
this.entities.put(id, new Entity(id, data));
}
void updateEntity(String id, EntityDataVO data) {
this.entities.get(id).update(data);
}
}
Entity
と値オブジェクトid
を持つEntityDataVO
クラスがあります。 AggregateRoot1
クラスは、このクラスのインスタンスを作成し、それらのリストを保持します。特定のエンティティへの更新は、Entity
classに委任されます。
次に、代替のモデリングアプローチを示します。
class AggregateRootDataVO {
private Map<String, EntityDataVO> entities = new HashMap<>();
AggregateRootDataVO addEntity(String id, EntityDataVO data) {
var rootData = new AggregateRootDataVO();
rootData.entities = new HashMap<>(entities);
rootData.entities.put(id, data);
return rootData;
}
AggregateRootDataVO updateEntity(String id, EntityDataVO data) {
var rootData = new AggregateRootDataVO();
rootData.entities = new HashMap<>(entities);
rootData.entities.put(id, data);
return rootData;
}
}
class AggregateRoot2 {
private AggregateRootDataVO data;
void addEntity(String id, EntityDataVO data) {
this.data = this.data.addEntity(id, data);
}
void updateEntity(String id, EntityDataVO data) {
this.data = this.data.updateEntity(id, data);
}
}
この場合、エンティティのリストはAggregateRoot2
の値オブジェクトAggregateRootDataVO
に「エンコード」されています。ご覧のように、値オブジェクトのIDは、もちろんそれ自体が値オブジェクトであるメンバーによって定義されます。したがって、entities
の内部マップが等しいこのクラスの2つのインスタンスは等しく、どちらを使用するかは気にしません。
今私は混乱しています。私は本当にどの代替案を使うべきか分かりません。 2番目のアプローチには間違いなく利点があります。完全な集計の状態は、値オブジェクトとして表されます。したがって、ネットワーク経由で送信するためにシリアル化するのは簡単です。たとえば、バックエンドサービスはそれをGUIフロントエンドに送信し、後者は完全なアグリゲートの状態を見ることができます。これは、AggregateRoot1
で実装された最初のアプローチには当てはまりません。その場合、AggregateRootDataVO
クラスに似たDTO(データ転送オブジェクト)を定義する必要があります。
他の人がこれらの2つのモデリングアプローチについてどのように考えているのか、どのような状況でどのアプローチを好むのかと思います。現在、どれを選べばいいのか本当にわかりません。実際、私は値オブジェクトとそのNiceプロパティを扱いたいので、それに対して正当な理由がないときは常に2番目のアプローチを好むように誘惑されます。
DDDは、質問に対する慣用的な回答を提供しません。最初のオプションでは、必要なコードが少なくなります。それで行く。
あなたが提供した2つの例の間に重要な違いはありません。それらはequalの同じAggregate
の実装です。同様に、updateEntity
メソッドに正確な署名と簡潔さを提供する以外に、EntityDataVO
が存在する理由はわかりません。
ここでの重要な洞察は、いずれかのオブジェクト(Entity
およびAggregate
)のconsumersがドメインの内部がどのように実装されているかを知らない(または気にしない)ことです。なぜこれに時間を費やしている(無駄にしている)のか?ビジネス要件を満たす最も簡単な方法を選択する必要があります。
広義では、用語を乱用しているため、いくつかの明確さを提供する必要があります。あなたのVOはDDDの意味での本当に値オブジェクトではなく、むしろ、不変性の独自の内部実装に使用される技術的なアーティファクトです。値オブジェクトはjust不変オブジェクトではありません。 またはドメインの概念です。これは、ビジネスの意味で、それが便利ドメインの表現またはドメインへの表現、あるいはその両方であることを意味します。
あなたの例は彼らのラッパーにのみ役立ちます。それは何も悪いことではありません! wantで、上で概説したアプローチを使用してエンティティの内部処理を不変としてモデル化する場合、それは特権です。 DDDの観点からは、違いはありません。
集約ルートの状態を、子エンティティも含む値オブジェクトとして完全にモデル化できることに気付きました。
はい。
基本的に、集計ルートはデータモデル全体を担当します。データモデルを個別の責任に分割することは「任意」です。
では、どのように選択するのでしょうか?現在、興味深いアイデアの1つは、ウォードカニンガムによる 技術的負債の説明 です。
プログラムを、金融オブジェクトについて考える適切な方法であると理解したものと一致させることができなかった場合、その不一致につまずきつづけることになり、それにより、ローンへの利息の支払いのように減速します。
より一般的には、私たちは正しい振る舞いを生み出すあらゆることを「できる」一方で、しかし、ドメインについて考えるための「適切な方法」に沿った設計を選択すると、将来の変更を管理する時間がはるかに簡単になります。
特に、ドメインエキスパートが「小さな」変更を要求した場合、実装への変更は小さい可能性が高くなりますif設計が整合している専門家がドメインについて考える方法で。