Aggregate root

Basic building blocks

Domain-Driven Design has couple of basic building blocks:

  • entities - types with identity
  • value objects - types without identity
  • aggregate roots - containers for entities and value objects

Those familiar with SQL modeling will recognize a mapping between this objects and relational database:

  • entity = table
  • value object = column
  • aggregate root = view starting from an entity

Advanced databases such as Oracle and Postgres support composite type as column in a table, while other databases use workarounds such as using multiple columns in a table to represent a value object (while this works for simple use cases, it falls short in more complicated scenarios). Since every aggregate has a primary key, it has also a unique representation exposed to the world: URI (unique resource identifier), which is just a string representation of it's primary keys.

Why are aggregates useful

Aggregate is used to define boundary around the object. For example in modeling Order with Line Items if we want to work with this object as a whole (one header and multiple items) - we will bound two entities inside this aggregate. Inside the aggregate boundary we will maintain consistency and business rules.

This will make it easier to reason about model, since we will reduce the number of first level objects in our communication. Our code will be simpler too since we will interact with aggregate as a whole and not its parts individually.

aggregate Order
{
  date placed;
  date? deadline;
  string? customerName;
  List<LineItem> items;
}

entity LineItem
{
  string productName;
  decimal quantity;
}

Every Order aggregate has its own unique identifier and when you fetch an Order aggregate you will receive all of its items too (while this looks wasteful in some situations, there are other concepts which can be used when we are interested only in parts of aggregate).

It's important to note that database implementation is optimized to work with these "objects". There is no Impedance mismatch in the database and we have an Order object which looks exactly like this model. If we do a SELECT by URI in the database we will get the whole object, which means that this object can be represented as key-value object, where URI is it's key.

Modeling and boundaries

Newcomers to DDD have problems drawing a boundary around aggregate. Since many people are used to work with entities, the most easiest way to start is to just convert all entities from the old model to aggregates in the new model.

With experience and understanding you will start to see boundaries and start down-casting aggregates to entities. Obvious sign that an aggregate should be an entity is if you want to disallow querying on it. Another sign is that you want to validate object as a whole (ie. don't allow Orders without at least one Line Item).

Aggregate should be used as a write boundary around objects. Many beginners for good reasons recognize that they want to use an aggregate in a slightly different way depending on the circumstances. This is fine, but it's not the aggregate they want to use, but the projection on an aggregate, which is a snowflake.

While aggregate maintain write boundary around an object, this doesn't mean that you have to send the whole object to the server whenever you want to change it. DSL Platform has support for rich modeling and there are appropriate concepts for such use case, such as Domain Events.