What belongs in an Aggregate?
Objects belong together when a business rule must remain true immediately after a command. Convenience, screen composition, or database joins are not sufficient reasons. Prefer small Aggregates and reference other Aggregates by identity.
- One transaction changes one Aggregate
- The Root is the only external entry point
- Commands express intent
- Domain Events record accepted facts
Invariants before structure
Start with rules such as “a confirmed order cannot accept new lines” or “credit cannot exceed the approved limit.” The objects and methods should exist to enforce those rules, rather than mirror a relational schema.
Common mistake
Large object graphs create contention and accidental coupling. If two concepts can become temporarily consistent through an event or process, they usually do not need to be in the same Aggregate.
Choose the boundary from invariants
Start by listing the rules that must be true immediately after a command. “An order cannot be confirmed without a shipping address” is an invariant. “The order screen displays customer details” is a query requirement and does not prove that Customer belongs inside the Order Aggregate.
If a rule can tolerate eventual consistency and be completed by a process reacting to an event, the concepts may belong to different Aggregates. This keeps transactions short and reduces contention while making the asynchronous business process explicit.
Commands, methods, and events
A Command expresses an intention such as PlaceOrder, AddOrderLine, or ConfirmOrder. The Aggregate Root accepts the command through a meaningful method, checks permissions and invariants, changes its state, and records Domain Events describing facts that were accepted. Events should not be emitted for attempted work that failed validation.
Keep decision-making inside the Aggregate when it depends on its own state. A handler or application service can load the Aggregate, invoke the behavior, persist the result, and publish events, but it should not reproduce the Aggregate’s business rules in a second location.
Aggregate size and references
Prefer small Aggregates with a focused responsibility. Reference another Aggregate by identity rather than holding a live object graph. This avoids loading unrelated state, prevents accidental cross-boundary mutation, and makes the transaction boundary visible.
An Aggregate Root is not necessarily an Entity that contains every related concept. Value Objects can express constrained concepts without identity; Domain Services can handle decisions that genuinely span multiple Aggregates; a Saga or Policy can coordinate the resulting events.
Concurrency and failure
Aggregates are also a concurrency tool. Optimistic version checks can detect two commands modifying the same version of an Aggregate. The system then retries, rejects, or asks the user to resolve the conflict according to business rules.
A failed Command should leave the Aggregate unchanged and should not publish a success event. A later failure in another context is different: the original fact remains true, and a compensating Business Action or Saga step may be required.
Aggregate review checklist
Before accepting an Aggregate design, verify that its boundary is justified by invariants rather than by database joins or UI composition.
- Can every mutation enter through the Root?
- Are the protected invariants written down?
- Does one transaction need all included state?
- Are other Aggregates referenced by identity?
- Are emitted events past-tense facts?
- What happens under concurrent commands?
DDDesigner
Example: Order Aggregate and fulfillment
The Order Aggregate owns lines, pricing decisions, customer reference, and the order lifecycle. PlaceOrder validates that the order has at least one line and that required data is present. ConfirmOrder checks the rules for confirmation and emits OrderConfirmed. Inventory and Payments are not nested inside Order: they react to events in their own contexts. A fulfillment Saga coordinates reservation, payment, and delivery, while the Order Aggregate remains responsible for order-specific invariants.
Start free