---
title: "Change an aggregate only through its root · Agent Wranglers"
url: https://agentwranglers.se/playbook/architecture/domain-model/aggregate-root
id: ARC-75
level: Recommended
updated: 2026-10-22
tokens: 1141
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Change an aggregate only through its root

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Domain model](https://agentwranglers.se/playbook/architecture/domain-model.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers.md). Updated 2026-10-22.

- ID: ARC-75
- Level: Recommended
- Chapter: [Domain model](https://agentwranglers.se/playbook/architecture/domain-model.md)
- Rule: Group objects that must stay consistent together into an aggregate, such as an order and its lines. Outside code changes them only through methods on the root, which check every business rule.
- Check: Outside the aggregate's module, no code writes to the child type or its table. A test for each business rule calls the root and gets the [domain exception](https://agentwranglers.se/playbook/architecture/errors-and-nulls/domain-exceptions.md) when the rule is broken.
- How: Say an order total must stay under the credit limit. When any code can add an order line directly, that rule lives in many places, and one forgets it. Give the root methods named after business actions, such as `addLine` or `cancel`, and keep the children private. Load and save the whole aggregate together. Keep aggregates small, since a large one locks too much at once. Model its states as [Model each state as its own type](https://agentwranglers.se/playbook/architecture/data-shapes/states-as-types.md) says, and its values as [Wrap domain values in their own types](https://agentwranglers.se/playbook/architecture/data-shapes/value-objects.md) says.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### TypeScript example

```ts
export class Order {
  private readonly lines: OrderLine[] = [];

  constructor(readonly id: OrderId, private readonly creditLimit: Money) {}

  addLine(product: ProductId, quantity: number, unitPrice: Money): void {
    const newTotal = this.total().plus(unitPrice.times(quantity));
    if (newTotal.isGreaterThan(this.creditLimit)) throw new CreditLimitExceeded(this.id, newTotal);
    this.lines.push(new OrderLine(product, quantity, unitPrice));
  }

  total(): Money {
    return this.lines.reduce((sum, line) => sum.plus(line.subtotal()), Money.zero());
  }
}
```

### Python example

```python
class Order:
    def __init__(self, order_id: OrderId, credit_limit: Money) -> None:
        self.id = order_id
        self._credit_limit = credit_limit
        self._lines: list[OrderLine] = []

    def add_line(self, product: ProductId, quantity: int, unit_price: Money) -> None:
        new_total = self.total() + unit_price * quantity
        if new_total > self._credit_limit:
            raise CreditLimitExceeded(self.id, new_total)
        self._lines.append(OrderLine(product, quantity, unit_price))

    def total(self) -> Money:
        return sum((line.subtotal() for line in self._lines), Money.zero())
```

## Sources

- [.NET architecture guide: the aggregate root pattern](https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/microservice-domain-model#the-aggregate-root-or-root-entity-pattern)

## Related rules

- ARC-55 [Model each state as its own type](https://agentwranglers.se/playbook/architecture/data-shapes/states-as-types.md) in Clean code and architecture
- ARC-50 [Wrap domain values in their own types](https://agentwranglers.se/playbook/architecture/data-shapes/value-objects.md) in Clean code and architecture
- ARC-27 [Use typed domain exceptions](https://agentwranglers.se/playbook/architecture/errors-and-nulls/domain-exceptions.md) in Clean code and architecture

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/architecture/domain-model/aggregate-root">Agent Wranglers clean code and architecture playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Give each bounded context its own model and words](https://agentwranglers.se/playbook/architecture/domain-model/bounded-contexts.md)
- Next: [Change one aggregate per transaction](https://agentwranglers.se/playbook/architecture/domain-model/one-aggregate-per-transaction.md)
- [Domain model](https://agentwranglers.se/playbook/architecture/domain-model.md)
