An aggregate is a group of domain objects that you change as one unit. It is a consistency boundary: its invariants must be true at the end of every transaction. An invariant is a rule that must always hold. The aggregate root is the one entity that outside code may reference and call.
Take an Order with OrderLines. It might have invariants like "the total may not exceed the credit limit" or "no lines can be added after payment". Callers use order.addLine(...), never order.lines.push(...). The root checks the invariants on every change. The lines are loaded and saved together with the order.
Common design rules, mostly from Vaughn Vernon:
- Keep aggregates small. Include only what a real invariant needs.
- Reference other aggregates by id (
customerId), not by object. - As a guideline, change only one aggregate per transaction.
- For rules that span several aggregates, use domain events with eventual consistency. This means the other aggregates are updated a little later, not in the same transaction.
- Have one repository per aggregate root, never one per child entity.
- An aggregate is a consistency boundary for its invariants.
- Outside code references only the root, and all changes go through it.
- Keep aggregates small, and reference other aggregates by id.
- As a guideline, change one aggregate per transaction and use events for rules across aggregates.
- Each aggregate root has one repository.