A DDD aggregate root enforcing consistency
An aggregate is a cluster of domain objects that a system treats as a single unit for the purpose of data changes. It has one entry point, the aggregate root, and a boundary that says what is inside. The point of drawing that boundary is consistency: every business rule that must hold true at all times holds true inside one aggregate, enforced in one transaction. The hard part is deciding where the line goes, because too large an aggregate kills concurrency and too small a one lets invariants leak.
Eric Evans introduced the aggregate in Domain-Driven Design (Addison-Wesley, 2003) as one of the tactical building blocks, alongside the entity and the value objectValue ObjectEngineeringA DDD value objectView reference →. He defined it as a cluster of associated objects treated as a unit for data changes, with a root entity as the only member outside code is allowed to hold a reference to. Anything inside the boundary can be reached only by going through the root. When a change commits, every invariant across the whole aggregate must be satisfied.
The thinking sharpened around how big an aggregate should be. Vaughn Vernon's Effective Aggregate Design (2011) argued for small aggregates and a rule that has since become doctrine: reference other aggregates by identity, never by direct object pointer. One aggregate holds the id of another, not the object itself. That keeps each aggregate loadable and savable on its own, and it draws the transactional line cleanly: one transaction changes one aggregate.
The corollary is that consistency between aggregates is eventual, not immediate. A change in one aggregate propagates to others asynchronously, often by a domain eventDomain EventEngineeringAn event published when something happens in the domainView reference →. The field accepted that trade because the alternative, sprawling transactions that lock half the model, scales badly and tangles the design. The boundary became the unit of both consistency and concurrency.
Take an Order aggregate in a retail system. The root is the order; inside it sit OrderLine entities and a Money total. The invariant is that the sum of the lines always equals the recorded total, and that an order over £500 requires manager approval before it can be confirmed. Both rules are checked inside the aggregate when any command runs against it, so the order can never be persisted in a state that breaks them.
A Customer lives in its own aggregate. The order does not hold the customer object; it holds a customerId. When a line is added, the order recalculates its own total in one transaction and stays internally consistent. Updating the customer's lifetime-spend figure happens separately, triggered by an event the order emits once it is confirmed. If that update lands a few hundred milliseconds later, nothing breaks, because spend was never an invariant the order had to guarantee.
In the Unified Product Graph, the aggregate is an engineering-region entity that records a consistency boundary in the domain model. A Bounded Contextmodelled asAggregatehierarchy edge ties it to the context it belongs to, and bounded_context_modelled_as_aggregateAggregatecontainsDomain Entityhierarchy and aggregate_contains_domain_entityAggregatecontainsValue Objecthierarchy edges record what sits inside the boundary. Its behaviour shows up through aggregate_contains_value_objectAggregatehandlesCommandhierarchy and aggregate_handles_commandAggregateemitsDomain Eventcausal, which connect the write side to the rest of the system. Modelling the aggregate explicitly makes the transactional line queryable: you can ask which entities must stay consistent together and which commands are allowed to touch them, rather than inferring the boundary from scattered code.aggregate_emits_domain_event
Type-specific fields on BaseNode
aggregate_rootstringRoot entity
invariantsstringEnforced business rules
idstringrequiredUnique identifier (UUID)
typeNodeTyperequiredDiscriminator for the entity type
titlestringrequiredDisplay name
descriptionstringOptional detailed description
statusstringLifecycle status
tagsstring[]Freeform tags for filtering
6 edge types connected to this entity.
bounded_context_modelled_as_aggregateaggregate_contains_domain_entityaggregate_contains_value_objectaggregate_handles_commandaggregate_emits_domain_eventread_model_projects_aggregate