Aggregate in Domain-Driven Design

An aggregate is a consistency boundary around a cluster of domain objects that must be changed according to defined business invariants. External code interacts with that cluster through its aggregate root rather than directly manipulating its internal entities.

An aggregate groups related domain objects into one unit for transactional consistency. The aggregate root is the externally accessible entry point and is responsible for protecting rules that must remain true whenever the aggregate changes.

Objects inside the boundary may include entities and value objects. An internal entity can have its own identity, but callers outside the aggregate should not hold references to it and mutate it directly; operations should normally be expressed through the root.

The boundary is also a transaction-design decision. A single transaction can reliably enforce invariants within one aggregate, while relationships between separate aggregates are commonly handled through application logic, domain events, messaging, or eventual consistency rather than one large transactional boundary.

Good aggregate design keeps the boundary as small as practical. Putting too many objects into one aggregate increases contention and makes transactions more expensive, while making the boundary too small can leave important business invariants impossible to enforce atomically.

In a Java backend, an aggregate can be represented with a root class whose methods express business operations and whose internal collections or entities have restricted access. Persistence frameworks such as JPA should not determine the domain boundary by themselves; the aggregate should reflect business invariants first.

Key points

Interview questions

How do you decide whether two domain objects belong in the same aggregate?

Start with the business invariants rather than database relationships. If a rule requires two objects to change atomically and consistently, that is evidence they belong within the same aggregate boundary. If they can change independently, separate aggregates may be more appropriate. Transactional requirements, contention, and access patterns should also influence the final boundary.

Why should external code modify an aggregate through its root?

The root provides a controlled API for maintaining the aggregate's invariants. If callers can directly mutate internal entities, they can bypass validation and leave the aggregate in an invalid state. Encapsulation also lets the domain model change its internal representation without exposing those details to callers. This makes business operations explicit instead of distributing rules throughout application code.

Should every aggregate operation run in a single database transaction?

Operations that must preserve an aggregate's invariants atomically are commonly handled within one transaction. However, an aggregate is a domain boundary rather than a synonym for a database transaction. Coordinating multiple aggregates in one transaction can create coupling and contention, so workflows often use domain events or other forms of eventual consistency when immediate atomicity is not required.

What is an aggregate in Domain-Driven Design?

An aggregate is a group of domain objects treated as one consistency boundary. It has an aggregate root that external code uses to perform changes. The root protects invariants across the objects inside the boundary. The design helps determine which changes should be atomic.

What problems can an aggregate that is too large cause?

A large aggregate increases the amount of state that must be loaded and potentially locked or updated for a single operation. It can also increase transaction duration, contention, and coupling between otherwise independent use cases. Large boundaries often indicate that unrelated invariants have been grouped together. Splitting the model around actual consistency requirements can reduce these costs while preserving the important business rules.