モデル階層の深いオブジェクトにコマンドを適用する正しい方法は何ですか?
次のモデルがあるとします。
public class Picture : AggregateRoot
{
private string title;
private List<Shape> shapes;
}
public class Shape
{
private ShapeType Type;
private Color color;
}
CreatePictureコマンドとPictureCreatedイベントを定義できます。
public class CreatePicture
{
Guid PictureId { get; set;}
string Title { get; set;}
}
public class PictureCreated
{
Guid PictureId { get; set;}
string Title { get; set;}
}
コマンドハンドラの簡単な実装は次のようになります。
var Picture = new Picture(cmd.PictureId, cmd.Title); // Internally store a PictureCreated event
repo.Add(Picture);
repo.Save(); // PictureCreated published
次に、AddShapeコマンドとShapeAddedイベントを定義できます。
public class AddShape
{
Guid PictureId { get; set;}
ShapeType Type { get; set;}
}
public class ShapeAdded
{
Guid PictureId { get; set; }
Guid ShapeId { get; set;}
ShapeType Type {get; set; }
}
ここでも、コマンドハンドラは次のようになります。
var picture = repo.Get<Picture>(cmd.PictureId);
picture.AddShape(cmd.ShapeId, cmd.Title);
repo.Save(); // ShapeAdded published
しかし、ここで、ChangeShapeColorコマンドとShapeColorChangedイベントを定義します。
public class ChangeShapeColor
{
Guid PictureId { get; set; }
Guid ShapeId { get; set;}
Color Color { get; set;}
}
public class ShapeColorChanged
{
Guid PictureId { get; set; }
Guid ShapeId { get; set;}
Color Color { get; set;}
}
コマンドハンドラーの実装方法がわかりません。
オプション1.すべての操作は画像を介して行われます。
var picture = repo.Get<Picture>(cmd.PictureId);
picture.ChangeShapeColor(cmd.ShapeId, cmd.Color);
repo.Save(); // ShapeColorChanged published
オプション2.操作は、画像から参照する形状を介して行われます。
var picture = repo.Get<Picture>(cmd.PictureId);
var shape = Picture.GetShape(cmd.ShapeId);
shape.SetColor(cmd.Color);
repo.Save(); // ShapeColorChanged published
オプション1は、より複雑なグラフの作成を開始するまでそれほど悪くはありません。例えば.
Building --(1..n)--> Floor --(1..n)--> Room --(1..n)--> Desk --(1..n)--> Equipment
また、集約ルートは「コントローラー」になり(これが間違った用語の場合は申し訳ありません)、実際にはドメインオブジェクトではなくなったようです。
これを処理する正しい方法は何ですか?モデルが実質的にフラットな場合、オンラインで簡単な例を見つけることができます。
よろしくお願いします。
DDDが示すように正しい方法で行う場合は、集計ルートで直接メソッドを使用することによってのみシェイプの色を変更でき、シェイプで直接行うことはできません。集約ルートは、内部エンティティを読み取り専用オブジェクトとして公開することのみが許可されています(たとえば、内部コレクションをIEnumerableとして公開する)。
ただし、優れた集計デザインは単純なデザインでもあり、集計ルートが大きくなる状況になったときに最初に確認する必要があるのは次の質問です:集計が実際に提供するコンテキストエンティティ?
あなたの場合、Shape
が本当に内部エンティティにすぎないのか、それともShape
がそれ自体で集約ルートになるのかを考える必要がありますか?
Shape
が複数のPicture
インスタンス間でいくつかのルールを定義する場合、Picture
クラスは、Shape
集約ルートの唯一の内部エンティティのままである必要があります。たとえば、画像に同じ色の2つの形状が含まれていない場合があります。このルールを定義すると、集約ルート(そのすべての形状を知っている)がこのルールを検証できるようになります。
ただし、Picture
集約ルートがそのようなルールを実際に定義せず、Shape
sを内部的にのみ格納する場合、Shape
は、次の構造を持つそれ自体が集約ルートである必要があります。
class Shape
{
private ShapeId shapeId;
private PictureId associatedPictureId;
private ShapeType type;
private Color color;
}
また、Picture
には次のメソッドがあります。
class Picture
{
private PictureId pictureId;
public Shape AddShape(ShapeId shapeId, ShapeType shapeType, Color color)
{
return new Shape(shapeId, pictureId, shapeType, color);
}
}
集約ルートは常に、集約ルートがその内部パーツに対して定義する境界の保護機能として考えてください。内部パーツの境界とルールを定義していない場合は、内部を個別の集約ルートに分割して、複雑さを取り除きます。
IMHOオプション1は、プログラミングの原則に最もよく適合するものです。複雑なグラフでは、ソリューションが物事を複雑にするように見えても、複雑ではありませんが、それが属するaggregate
の内部に複雑さを保ちます。
オプション2は、重要な原則である デメテルの法則 を破ります。また、複雑なグラフの場合は、Application services
の複雑さ/知識を移動し、本来あるべき状態に近づきます。