How to load an aggregate?

1.1k Views Asked by At

In the articles/books on ddd in the chapters that treat aggregates I have the feeling that a subject is never discussed :

How to reload an aggregate ?

  • Either it's too obvious and it's useless to talk about it
  • Either it's not the right thing to do
  • Either I don't understand

If I take the example of the order with these items create or update my invariants are the same. Let's say that I have to update a price on an item of an already existing order, so I will start by reloading my aggregates.

But how do I do this ?

  1. Use a tool such as automapper which will be able to set the private property ?

  2. Use a constructor that takes the id of the root aggregate, the child repositories and assign the private property in this constructor ?

  3. Rebuild everything from scratch item by item as if I was creating a new one ?

2

There are 2 best solutions below

1
On

Rebuild everything from scratch item by item as if I was creating a new one ?

Usually this one.

Load the data from the persistent store using whatever the appropriate general purpose mechanism is: an array of bytes, a json document, record sets, whatever. Now that you have the information in memory, copy that information into a factory that produces your domain model objects.

Often, your design will include some manifestation of the factory pattern that creates an aggregate from other information - you can often use a similar factory behind the facade of your repository to achieve the same results on a reload.

0
On

You ask about a system to reload aggregates, but this system should also be able to create and to update aggregates. When creating them, you want to avoid duplicates. When updating them, you want to save the whole aggregate transactionally and you want to avoid that concurrent updates leave an inconsistent state in the database. Think of two separate threads adding a line item for the same product each and you don't allow duplicate products. The order is consistent on each thread but if you don't prepare for this, in a relational database, you can end up inserting the duplicate line items.

You also want to isolate your core code from your data access code. To achieve that, I normally use the following interface:

public interface IOrderUnitOfWork
{
    Task<Order> GetOrCreate(Guid id, Func<Task<Order>> createFunc);
    Task<Order> Get(Guid id);
    Task Commit();
}

This works as follows:

  1. For use cases where the aggregate needs to be created, I use GetOrCreate. The reason for that is to make the operation idempotent. The first time you call the operation, it'll call the createFunc. If you retry the operation, it'll find the Order and it won't be created again.

  2. For use cases that the aggregate should be there already (Add line item, cannot work without an order) I use Get. This will throw a NotFoundException if the Order does not exist. This makes your code cleaner saving hundreds of null checks in your use cases.

  3. At the end of every use case, I call Commit always. The UnitOfWork should track the aggregate that it loaded and it knows if there have been changes or not. If there have been changes, it saves the whole aggregate transactionally and with optimistic concurrency checks, so that if another process changed the aggregate in the DB, the Commit will fail.

Now you can implement your use cases against this interface regardless of the technology you use to implement it. I've used an ORM (Entity Framework) and json based data stores. Storing the aggregate as a document has the advantage that it's transactional/atomic by default. If you use a relational database with an ORM, you can make an ORM map your aggregate directly to the database, so you don't have to create duplicates of all your classes in your data access layer and then use Automapper or manually map your DAO objects to your core objects, as this will mean a big amount of code that doesn't add any value.

You could also consider Event Sourcing, so you'd store the aggregate state as a series of events that applied sequentially construct the current state of your aggregate (OrderCreated, LineItemAdded, LineItemAdded, LineItemChanged, etc). In that case, the implementation of the unit of work would load the events and your aggregate would have some sort of ApplyEvents method to construct the current state.