Aggregates are Boxes. It’s about the Box. Not what’s inside
This surprises many engineers.
An object is consistent when it maintains its invariants. To maintain invariants in a complex system with parallel user, you need to know where an object begins and where it ends. This is the only way to isolate one user’s workflow from another’s while also maintaining respective invariants.
This is what an Aggregate’s purpose is. To declare where an object graph ends. By controlling its boundaries, the aggregate also controls the lifecycle changes. However, it is completely acceptable for an Aggregate to be created by a Factory, Repository or Service and deleted by a Repository.
A tight aggregate can have complex rules. Simple rules can have simpler (wider) aggregates. But in general, maintaining any level of meaningful business invariants is challenging without proper Aggregate boundaries.
Design Pattern or Business Concern?
Looking purely at the code, Aggregates don’t really have a pattern to them. They may look like entities, records, adodb records. There’s nothing really fascinating about them. So what’s the big deal?
The magic happens when you start thinking of them as roles and concerns that your non-tech business stakeholders keep mentioning in discussions about your business processes and domain concerns. Things like Charge, Churn, Reservation, Account rather than transaction, log, event or user.
It comes down to shaping a logical boundary that envelops your main business validation rules - the invariants.
Invariants are consistency rules that must be maintained whenever data changes.
- Eric Evans 2003, Domain-Driven Design
But Denis, I never used Aggregates before and it was fine
Okay, that’s a great point! Let’s explore that…
👆 What’s the benefit?
Aggregates help you write code that doesn’t keep passing around references to repositories
💩 You can smell a very strong signal a mile away whenever you are passing around or dependency injecting a handful of repositories into your services or controllers. That is a natural code smell of anemic domain models.
Anemic domain models only contain data and a little bit of proxy-action going on to access transient entities. The critical code that you’ll want to test is where the data invariants are enforced through method calls that create some kind of behavioural state-transition.
You may have heard suggestions to use controllers, presenters or “domain services” for this. Did it ever seem weird to you? It is. The most practical solution is to shape an Aggregate instead.
And now we have come full circle.
Find segments of code in your existing codebase where you manually manage transactions around entities and you will find an unclaimed aggregate boundary.
Give it a name, figure out what defines its identity and you’ll be making a huge step towards modularity using DDD.
Meaningful Aggregates have Simple, Trustworthy Tests
Another downstream benefit of having your team design well-crafted Aggregates is that they are ridiculously easy to isolate, test and interact with. They provide you with the ability to write simple, low-code one liners for code examples, documentation and DevEx/DevRel tutorials.
Stripe is well-known for having an exemplary API. I’d attribute most of their success to inviting Domain Experts into their API design sessions and having very good documentation thanks to their focus to keep their entire API centred around their main Aggregates in DDD style.
I invite you to take a look at the API documentation for charges. Now that you know what to look for, I’d love to know what you learn from it.