Aggregate
An aggregate is a cluster of entities and value objects treated as one unit for data changes, with a root that guards its invariants. Every change goes through the root, so the invariant holds at the boundary. It often emits a Domain Event when its state changes.
Read through intent and mechanism, the aggregate is an intent (a consistency boundary) welded to a 2003 mechanism (one database transaction, one hand-rolled repository). The intent is durable; the mechanism is the dated part, which is why the aggregate looks more assumption-bound than a bounded context. The field’s sharpest tactical rule, one transaction per aggregate, is a post-Evans elaboration (the 2003 book only implies it) and is itself contested: Vernon allows exceptions, and Millett and Tune go further.
A tactic that ages well: encode invariants in the type system so invalid states will not compile. In Kotlin, a sealed hierarchy does this.
sealed interface Cart {
data object Empty : Cart
data class Active(val head: Line, val rest: List<Line>) : Cart
}
Signal: every change to the cluster goes through one root, and the invariant it protects can be stated in a sentence. Part of Domain-Driven Design.
Sources
- Eric Evans, Domain-Driven Design Reference (2015): the aggregate as a consistency boundary; the one-transaction rule is only implied, not stated.
- Vaughn Vernon, Implementing Domain-Driven Design (2013): the “one transaction per aggregate” rule worked out in code, and the exceptions it allows.
- Scott Millett and Nick Tune, Patterns, Principles, and Practices of Domain-Driven Design (2015): pushes past that rule further still.