As an application begins to grow in complexity, the web of interconnected Entities and associations can begin to get overwhelming. What was once a simple set of Entities and relationships is suddenly a sprawling mess of objects and associations.
When the number of interconnected objects increases, so too does the surface area for interacting with those objects. Certain objects will have child objects, and those objects will have further related objects.
Domain Driven Design is all about allowing the model to drive the direction of development. When the complexity of the object graph inevitably becomes overwhelming, it’s time to take a closer look at the model to understand how to resolve the issues.
Aggregates are a way to calm this complexity by reducing the web of connected objects into a single unit. In today’s article we’re going to be looking at Aggregates, how they work and why you would want to use them in a Domain Driven Design project.
The problem of complexity
When code becomes too complex, bugs can creep in through ambiguity.
In a simple application, all objects will have equal precedence within the application. For example, if we were creating a forum application, we would probably have
Reply objects. Each
Thread would have child
Post objects and certain
Post objects would have
When all objects have equal precedence, we could very easily select any of these objects straight from the database using their unique id:
$reply = Reply::find(4526);
However, what about the rules of our application? If only certain users can post in a
Thread how do we prevent them from adding a
Reply object to a
Post? What about when a
Thread is deleted? How do we deal with all of the child
Post objects and their
Looking to the model for answers
In any non-trivial application, there is likely going to be a lot of rules around how the objects should interact and coexist together. In reality, these objects should not have equal precedence in the application.
Post object only really make sense in the context of a
Thread because we can’t have a
Post without a
Thread, and we definitely can’t have a
Reply without a
When all objects have equal precedence we have created a massive interface for interacting with those objects. However, when we actually take a closer look at the model we are building, it does not make sense for those objects to be globally accessible. If an object shouldn’t appear outside of the context of it’s parent, we have no business having access to it in isolation.
Aggregates are the answer
An Aggregate is a cluster of associated object that we treat as a single unit. Each Aggregate has a single root Entity and a boundary that marks what is inside and what is outside of the Aggregate.
The root Entity is the only Entity that is globally accessible in the application. In the example from above,
Thread would be the root Entity.
Inside the boundary we would have all of the associated objects of this Aggregate. For example, we would have the
Reply Entities as well as any other Entities or Value Objects that make up the Aggregate.
Reply objects can hold references to each other internally to the Aggregate, but no other external object can hold a reference to any object internally to the Aggregate that is not the root Entity.
The only way to access the
Reply Entities is to retrieve the
Thread root Entity and traverse it’s associated objects.
The benefits of using Aggregates
To fully appreciate why you would want to structure your application around Aggregates, I think its important to first understand the benefits of using them. Without understanding the benefits, Aggregates can seem a bit like a needless artificial constraint in your code.
I think there are basically 3 benefits to using Aggregates.
A simple interface
The first big benefit of Aggregates is that it greatly reduces the interface you have to a certain segment of your application.
Imagine it’s your first day on the job of our imaginary forum development project and you’ve been tasked with making a change to some existing functionality.
Having unrestricted power to manipulate any object in the application is only going to lead to bad things happening. Without the constraints of an Aggregate a developer without the understanding of the business rules of the application will likely break those rules or expose an object that should not be exposed.
By having a single point of entry to the Aggregate, we can constrain access to the internal objects and maintain the harmony inside of the boundary. Having a single point of entry also makes it much easier to work with the code for the first time because there is no ambiguity of how you are supposed to access the child objects of a parent object.
Maintaining the invariants
The real beauty of Domain Driven applications is the ability to model the business rules of the application through invariants. An invariant is a rule that must remain consistent.
For example, if there was a business rule that stated only users who had posted in a thread could reply to other posts, we would be able to maintain this rule through the constraint of the Aggregate.
Maintaining the invariants of a unit of objects would be difficult without the constraint of the Aggregate boundary. If we didn’t have the boundary it would be very easy to simply create a new
Reply and associate it with a
Thread can be in charge of deciding whether a
Reply is valid or not, and so any attempt to circumvent this rule would be stopped.
A common problem when building web applications is how to safely remove objects. When we don’t have constraints around what objects can be associated with other objects, this can start to become scary.
For example, if we didn’t restrict the associations of the application and we wanted to delete a
Thread, how could we be certain that we wouldn’t be breaking other associations by cascading through the relationships of the
If we were to delete just the
Thread we would be leaving lots of
Reply garbage objects in the database that no longer had context within the application.
Using Aggregates removes this problem because all objects within the boundary of the Aggregate are forbidden from being associated with any object outside of the boundary. This means we can delete the
Thread and all of it’s internal associated objects without being scared of breaking associations across the application.
Thinking about your application as a set of main Aggregates can really clarify your thought process when designing what you need to build. Aggregates attract responsibility and capture a lot of the nuances of an association between two objects so it is not lost in complexity.
Now of course, there will always be ways to circumvent the constraints that an Aggregate puts in place. But I think the process of identifying and modelling your application in terms of Aggregates can really help distill the problem you are trying to tackle.
Using Aggregates also makes it a lot easier to make certain decisions. For example, it can be difficult to decide who has responsibility for creating and maintaining the relationship between Entities. When you identify and implement Aggregates you don’t have to ponder this decision because the model should have already guided you to the right answer.
So even if your next project isn’t a fully blown Domain Driven Design application, I would encourage you to start thinking about your code in terms of Aggregates and where the lines of responsibility between objects fall.