What is the correct way to apply commands to objects deep within the model hierarchy?
Given the following model:
public class Picture : AggregateRoot
{
private string title;
private List<Shape> shapes;
}
public class Shape
{
private ShapeType Type;
private Color color;
}
We can define a CreatePicture command and PictureCreated event:
public class CreatePicture
{
Guid PictureId { get; set;}
string Title { get; set;}
}
public class PictureCreated
{
Guid PictureId { get; set;}
string Title { get; set;}
}
A simple implementation of the command handler might look like this:
var Picture = new Picture(cmd.PictureId, cmd.Title); // Internally store a PictureCreated event
repo.Add(Picture);
repo.Save(); // PictureCreated published
Then we can define an AddShape command and ShapeAdded event:
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; }
}
Again, the command handler looks like this:
var picture = repo.Get<Picture>(cmd.PictureId);
picture.AddShape(cmd.ShapeId, cmd.Title);
repo.Save(); // ShapeAdded published
But now, we define a ChangeShapeColor command and ShapeColorChanged event:
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;}
}
I'm not sure how the command handler should be implemented:
Option 1. All operations go via the Picture:
var picture = repo.Get<Picture>(cmd.PictureId);
picture.ChangeShapeColor(cmd.ShapeId, cmd.Color);
repo.Save(); // ShapeColorChanged published
Option 2. Operations go via the shape, which we look up from the Picture:
var picture = repo.Get<Picture>(cmd.PictureId);
var shape = Picture.GetShape(cmd.ShapeId);
shape.SetColor(cmd.Color);
repo.Save(); // ShapeColorChanged published
Option 1 doesn't seem too bad until we start building up a more complicated graph. e.g.
Building --(1..n)--> Floor --(1..n)--> Room --(1..n)--> Desk --(1..n)--> Equipment
It also seems like the aggregate root has turned in to a 'controller' (sorry if this is the wrong terminology) and is not an actualy domain object anymore.
What is the correct way to handle this? I can only find trivial/simple examples online where the model is practically flat.
Many thanks in advance.
2 Answers 2
If you want to do it the right way as DDD suggests, then you are only allowed to change shape colour by using a method on the aggregate root directly and you cannot do it on the shape directly. The aggregate root is only allowed to expose its internal entities as read-only objects (e.g. exposing internal collections as IEnumerable).
However, good aggregate design is also a simple design and when you come to a situation in which your aggregate root grows, the first question you should ask yourself is: Which context does the aggregate actually provide for the entities?
In your case, you should think whether the Shape
really is just an internal entity, or whether the Shape
could be an aggregate root on its own?
Your Shape
class should remain an only internal entity of the Picture
aggregate root when the Picture
defines some rules among multiple Shape
instances, such as: A picture may not contain two shapes of the same color. Having defined this rule, the aggregate root - knowing about all of its shapes - can now verify this rule.
But, if the Picture
aggregate root does not really define any rule as such and only internally stores the Shape
s then the Shape
really should be an aggregate root on its own with the following structure:
class Shape
{
private ShapeId shapeId;
private PictureId associatedPictureId;
private ShapeType type;
private Color color;
}
and the Picture
would have the following method:
class Picture
{
private PictureId pictureId;
public Shape AddShape(ShapeId shapeId, ShapeType shapeType, Color color)
{
return new Shape(shapeId, pictureId, shapeType, color);
}
}
Always think of an aggregate root as a protector of boundaries which the aggregate root defines for its internal parts. If it does not define any boundaries and rules for its internal parts, split the internals into separate aggregate roots to remove complexity.
IMHO option 1 is the one that conforms best with the principles from programming. Even if it seems that in complex graphs the solution complicate things, it does not, it keeps the complexity inside the aggregate
, where it belongs.
Option 2 breaks the Law of Demeter, an important principle. Also, for complicate graphs, it moves the complexity/knowkedge in the Application services
which become more inteligent that they should be.
-
I think that the implementation might be confusing the theory for me. I guess that all commands operate on an Aggregate (which is multiple domain objects) via the Aggregate Root only (which is the top object in the bounded context)?Karle– Karle2017年08月20日 09:54:35 +00:00Commented Aug 20, 2017 at 9:54
-
1Indeed, the
Aggregate root
is the only way to access its nested entities. You should not assume/depend on any internal structure. You should protectAggregates
encapsulation.Constantin Galbenu– Constantin Galbenu2017年08月20日 10:07:55 +00:00Commented Aug 20, 2017 at 10:07
Explore related questions
See similar questions with these tags.