How do you compose an aggregate? For me, aggregate design involves understanding the invariants. Invariants are business rules that must always be consistent. Understanding the invariants will guide your aggregate design. Everything I seemingly post ends up being about defining boundaries! Aggregates are yet another example of defining boundaries based on invariants and consistency.
Check out my YouTube channel where I post all kinds of content that accompanies my posts including this video showing everything that is in this post.
The example I’m going to use is the concept of a Shipment. You could think of this as a food delivery service. You have food that gets picked up at a restaurant and delivered to your house. The shipment consists of 2 stops. The Pickup, which is the restaurant, and the Delivery which is your house.
The important aspect about a stop is they have to go through a state transition. The initial state of a stop is In Transit. Once the delivery person arrives at the restaurant to pick up the food, the stop then enters the Arrived state. Once the delivery person leaves the restaurant with the food and on their way to your house, the stop is now in a Departed state.
There are a couple of invariants within our shipment.
- A stop must progress through its state transitions in the exact order outlined above.
- The delivery stop cannot arrive until the pickup stop has departed
The first invariant is fairly easy to control as we can have that logic within the stop itself.
The second invariant that the delivery stop cannot arrive until the pickup start is departed is a bit more difficult.
The issue is that an individual stop does not know about the other stops. Meaning the pickup stop object has no access to the delivery stop object. This is where an aggregate comes in.
An aggregate is a collection of domain objects that form a consistency boundary.
Our aggregate consists of the Shipment and Pickup and Delivery Stops. The Shipment is what is called the Aggregate Root.
The aggregate root is the gateway to all interactions within the aggregate. In other words, you only expose the aggregate root. The calling code cannot access Stops directly. Because of this, you can enforce invariants for the entire aggregate.
In the Arrive() method, we confirming that all prior Stops have been departed. This enforces that the Stops are done in the correct order. Meaning that the Pickup stop is going through its full state progression before we can start the state progression of the Delivery.
Aggregate Design: Invariants
Use invariants as the guide to designing your aggregates. Invariants are business rules that must always be consistent. If you have invariants and are not using an aggregate, I recommend modeling it to try it out for yourself. You’ll have fewer wonders about how the state changed because all interactions must go through the aggregate root. There’s only one-way state can change.
Developer-level members of my CodeOpinion YouTube channel get access to the full source for the working demo application available in a git repo. Check out the membership for more info.