
Created on: 8/28/2025
Updated on: 8/29/2025
4 mins
Tactical Design with Aggregates
So far in this series, we’ve looked at the big picture: domains, subdomains, bounded contexts, and how they interact
through context mapping.
Now it’s time to zoom in — into the building blocks of a model.
One of the most important tactical patterns in Domain-Driven Design is the Aggregate.
Aggregates help us protect business rules, keep our models consistent, and avoid the trap of turning everything into a
chaotic object graph.
Why Aggregates Matter
An aggregate is more than just a collection of entities and value objects.
It is a consistency boundary: the place where we make sure business rules are enforced and invariants are protected.
Without aggregates, it’s easy to fall into the anemic model trap: code where entities are little more than data containers, and all the real logic lives elsewhere. This leads to fragile systems where business rules are scattered and inconsistencies creep in.
With aggregates, we bring the rules back into the domain model itself — close to where the data lives.
Rules of Thumb for Designing Aggregates
Over the years, some practical guidelines have emerged for working with aggregates.
Protect Business Invariants
Each aggregate is responsible for keeping its own rules intact.
For example, an Order
aggregate might enforce that it cannot be paid twice, or that it must always have at least one
item.
Keep Aggregates Small
Large aggregates that span too much of the domain quickly become bottlenecks.
Aim for the smallest boundary that still protects the business rules.
Reference by Identity Only
Instead of holding references to entire other aggregates, keep only their identity (like an OrderId
or
CustomerId
).
This prevents accidental loading of half the system into memory.
Eventual Consistency
When an action in one aggregate affects another, don’t force everything into a single transaction.
Let the first aggregate emit an event, and let the other react asynchronously. This makes the system more scalable and
resilient.
Modeling Aggregates in Practice
Aggregates aren’t designed in isolation. They work together with other DDD building blocks.
Use Value Objects Liberally
Value objects make aggregates cleaner. Instead of spreading raw primitives everywhere, wrap concepts like Money
,
Address
, or DateRange
into explicit types.
This makes invariants easier to enforce and keeps the aggregate logic focused.
Right-Sizing Aggregates
Don’t try to model the real world literally. The question is not “What does a customer look like in reality?” but
“What boundaries make sense in this system?”
Sometimes, a “Customer” is its own aggregate. Sometimes, it’s just a reference inside another aggregate.
Choose Abstractions Wisely
If creating an aggregate feels too heavy, perhaps a simpler entity or value object will do.
Conversely, if rules are leaking all over the place, it might be time to elevate a concept into its own aggregate.
Factories for Complex Creation
When creating an aggregate requires non-trivial setup (for example, constructing a Loan
aggregate with multiple rules
and sub-objects), use a Factory.
This keeps the construction logic consistent and prevents developers from bypassing important invariants.
Example: Orders and Payments
Consider an e-commerce system:
- The Order aggregate protects rules like “an order cannot be paid twice” or “an order must have items.”
- The Payment aggregate manages its own lifecycle — initiated, confirmed, failed.
- Instead of embedding
Payment
directly insideOrder
, theOrder
keeps only aPaymentId
. - When an order is paid, it emits an event (
OrderPaid
) which the Payment context consumes asynchronously.
This way, each aggregate protects its own invariants, and the system as a whole stays consistent without everything being locked into one transaction.
Closing Thoughts
Aggregates are the tactical heart of a domain model.
They give us a way to enforce rules where they matter most, without coupling everything into a single fragile structure.
By keeping aggregates small, using value objects, referencing by identity, and relying on events for coordination, we
create models that are both expressive and scalable.
And when creation gets complex, factories help us keep the process consistent.
Most importantly: aggregates remind us that the model is not just about data. It’s about behavior, rules, and meaning — the essence of Domain-Driven Design.