Domain-Driven Design – Handling Aggregate Root with Deep Object Hierarchy

aggregatecqrsdomain-driven-designevent-sourcing

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.

Best Answer

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 Shapes 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.

Related Topic