Aggregate
A cluster of domain objects (entities and value objects) that are treated as a single unit for the purpose of data changes, with one object designated as the Aggregate Root that controls all access to and modification of the cluster.
Problem
Domain objects rarely exist in isolation. An Order contains OrderLines; a Customer has Addresses. Without a principled way to group them:
- Invariants (business rules) spanning related objects are hard to enforce.
- Concurrent modifications to related objects can corrupt data.
- Determining transaction boundaries becomes arbitrary.
Solution / Explanation
An Aggregate groups a cluster of entities and value objects into a single consistency boundary. The Aggregate Root is the single entry point:
- External objects may only hold references to the Aggregate Root, never to internal entities directly.
- All state changes must go through the Aggregate Root, allowing it to enforce invariants.
- A single database transaction should span at most one aggregate; cross-aggregate consistency uses eventual consistency and domain events.
Aggregate Root
The root entity that:
- Has a global identity (ID) that persists outside the aggregate.
- Enforces all invariants (business rules) for the cluster.
- Controls the lifecycle of all child entities.
- Raises domain events when state changes occur.
Invariants
Business rules that must always hold true within the aggregate boundary. Example: “An order’s total cannot exceed the customer’s credit limit.” The aggregate root checks this rule on every modification.
Design Heuristics
- Keep aggregates small. Large aggregates cause lock contention and load performance issues. Prefer many small aggregates over one large one.
- Reference other aggregates by ID, not by object. Cross-aggregate navigation happens outside the aggregate, via repository lookups.
- Design around true invariants. Ask: “What must be consistent immediately?” Only put objects that share an invariant into the same aggregate.
- Use eventual consistency between aggregates. A command on Aggregate A publishes a domain event; Aggregate B handles it asynchronously.
- Law of Demeter applied (Laribee: “Don’t talk to strangers”): External code should only call methods on the Aggregate Root — never traverse into internal entities to call their methods.
order.getLine(id).cancel()violates this;order.cancelLine(id)respects it. This ensures all invariant checks flow through the root. - Behavior over data: Aggregates should expose operations, not data getters. An
Ordershould haveaddItem(product, qty)rather thangetItems().add(...). Tests should assert behavioral outcomes, not internal state fields.
Key Components
- Aggregate Root — the single point of access and the entity with global identity.
- Internal Entities — entities that only make sense within this aggregate; referenced by local ID.
- Value Objects — immutable descriptors used within the aggregate (e.g.,
Money,Address). - Invariants — rules the root enforces on every state change.
- Domain Events — raised by the root to signal state changes to the outside world.
When to Use
Apply the Aggregate pattern in any domain that has:
- Groups of objects with shared business rules.
- Concurrency concerns around related data.
- Clear transactional boundaries that align with business operations.
Trade-offs
| Benefit | Drawback |
|---|---|
| Enforces invariants at the correct granularity | Aggregate boundaries are hard to get right initially |
| Clear transactional scope | Large aggregates cause lock contention |
| Protects internal consistency | Cross-aggregate operations require eventual consistency |
| Reduces coupling between unrelated objects | Requires rethinking relational DB schemas |
Related
- Domain-Driven Design — the framework this pattern belongs to
- Bounded Context — aggregates live inside bounded contexts
- Value Object — immutable components used inside aggregates
- Domain Event — raised by aggregate roots to communicate changes
- Repository Pattern — the mechanism for loading and saving aggregates
- Event Sourcing — aggregate state can be rebuilt from its event history