# Aggregates

> A cluster of domain objects changed as one unit through a single root, which enforces its invariants — the consistency boundary of a DDD model.

Concept · Architecture & domain modeling · https://levelcode.org/concepts/aggregates

An **aggregate** is a group of entities and value objects that is treated as a single unit for changes, with one entity as its **root**. It exists to protect **invariants** — business rules that must always hold — such as "an order's total equals the sum of its lines" or "a placed order can't be edited".

## The rules

1. **Outside code references only the root.** It may hold the root and call its methods, but never modify an inner object (an order line) directly.
2. **The root enforces every invariant** in its methods, so no code path can leave the aggregate invalid.
3. **One transaction modifies one aggregate.** Inside the aggregate, consistency is immediate; between aggregates, it's eventual — coordinated through domain events.
4. **Aggregates refer to each other by ID**, not by object reference: an `Order` holds a `CustomerID`, not a `*Customer`.
5. **One repository per aggregate root**, loading and saving it whole.

```go
type Order struct { // root
	id     OrderID
	lines  []OrderLine // unexported: only Order's methods change it
	status Status
}

func (o *Order) AddLine(item ItemID, price Money, qty int) error {
	if o.status != StatusDraft {
		return ErrNotEditable
	}
	if len(o.lines) >= 50 {
		return ErrTooManyLines
	}
	o.lines = append(o.lines, OrderLine{item: item, price: price, qty: qty})
	return nil
}
```

The contrast is an **anemic model**: structs with public fields and all rules in service functions, where any code can write `order.Status = "PLACED"` and skip the checks.

## Sizing aggregates

Keep them **small**. Include only what must be consistent in the same transaction. A `Restaurant` aggregate containing every order ever placed would lock all of a restaurant's orders on each change and load huge amounts of data. Instead, `Order` and `Restaurant` are separate aggregates, and a rule spanning them ("can't order from a closed restaurant") is checked by the use case before calling the order, or handled eventually through events.

Ask of each invariant: *does it really need to be true at every instant, or within seconds?* Many rules that look immediate — loyalty points, stock counts across warehouses, analytics — tolerate eventual consistency, which keeps aggregates small and fast.

Aggregates are built from [value objects](https://levelcode.org/concepts/value-objects) and entities, and publish changes through events, often via a [transactional outbox](https://levelcode.org/concepts/transactional-outbox). See the [domain-driven design lesson](https://levelcode.org/roadmaps/software-engineer/domain-driven-design).
