Tactical Design with Aggregates

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 inside Order, the Order keeps only a PaymentId.
  • 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.