The aggregate: where “must never happen” is guaranteed
Use an aggregate as the transactional boundary that makes invariants structurally impossible to break.
In Module 3 you wrote down the invariants for the workspace: a workspace must never exceed its seat limit; a teammate must never be invited twice. You called these your failure model — the things that must never happen. The aggregate is the structural answer to that list. It’s the thing that makes those rules impossible to break.
An aggregate is a cluster of objects you treat as a single unit for the sake of consistency. You load it whole, change it whole, and save it whole, in one transaction. Because the seat-limit check and the act of adding a member happen inside that one boundary, in one atomic step, there is no moment at which the rule can be violated.
That’s what we mean by a transactional boundary: everything that must be true together lives inside it; everything that’s allowed to drift apart for a while lives in a different aggregate.
It has one entry point — the aggregate root — and the outside world only ever holds the root. Nobody reaches past it to adjust a member directly, because reaching past it is precisely how an invariant gets bypassed. Same instinct as the command handler in Module 2: one obvious door, so there’s one obvious place the rule is enforced.
// inside the Workspace aggregate — the rule lives here, not in the handler
Workspace.invite(email):
if members.count >= plan.seatLimit: reject SeatLimitReached
if members.contains(email): reject AlreadyAMember
members.add(Member(email))
record(TeammateInvited(this.id, email)) // the event, emitted on success
Illustration note: the aggregate as a sealed boundary — the root as the single door, entities and value objects inside, the invariant drawn as a guard on the door. Outside code touches only the root. This is the second diagram worth carrying, after the hexagon.
Here is the single most important thing the boundary buys you, said plainly: it turns “we must remember to check the seat limit everywhere we add a member” into “the seat limit cannot be exceeded, structurally, ever.”
The whole purpose of the aggregate is to convert your failure model from discipline you have to remember into a property of the code — which is exactly what you want when an agent, not you, is writing most of the lines.