home / skills / yonatangross / orchestkit / aggregate-patterns

aggregate-patterns skill

/plugins/ork/skills/aggregate-patterns

This skill helps you design robust DDD aggregates with clear boundaries and invariants, enforcing rules and optimizing cross-aggregate references.

npx playbooks add skill yonatangross/orchestkit --skill aggregate-patterns

Review the files below or copy the command above to add this skill to your agents.

Files (6)
SKILL.md
5.7 KB
---
name: aggregate-patterns
description: DDD aggregate design patterns for consistency boundaries and invariants. Use when designing aggregate roots, enforcing business invariants, handling cross-aggregate references, or optimizing aggregate size.
context: fork
agent: backend-system-architect
version: 1.0.0
tags: [ddd, aggregate, consistency, invariants, domain-modeling, python, 2026]
author: OrchestKit
user-invocable: false
---

# Aggregate Design Patterns

Design aggregates with clear boundaries, invariants, and consistency guarantees.

## Overview

- Defining transactional consistency boundaries
- Enforcing business invariants across related entities
- Designing aggregate roots and their children
- Handling references between aggregates
- Optimizing aggregate size for performance

## Core Concepts

```
┌─────────────────────────────────────────────────────────┐
│                 ORDER AGGREGATE                         │
│  ┌─────────────────────────────────────────────────┐   │
│  │         Order (Aggregate Root)                   │   │
│  │  • id: UUID (UUIDv7)                            │   │
│  │  • customer_id: UUID (reference by ID!)         │   │
│  │  • status: OrderStatus                          │   │
│  └─────────────────────────────────────────────────┘   │
│           │                      │                      │
│  ┌────────────────┐    ┌────────────────┐              │
│  │  OrderItem     │    │  OrderItem     │              │
│  │  (child)       │    │  (child)       │              │
│  └────────────────┘    └────────────────┘              │
│                                                         │
│  INVARIANTS enforced by root:                          │
│  • Total = sum of items                                │
│  • Max 100 items per order                             │
│  • Cannot modify after shipped                         │
└─────────────────────────────────────────────────────────┘
```

### Four Rules

1. **Root controls access** - External code only references aggregate root
2. **Transactional boundary** - One aggregate per transaction
3. **Reference by ID** - Never hold references to other aggregates
4. **Invariants enforced** - Root ensures all business rules

## Quick Reference

```python
from dataclasses import dataclass, field
from uuid import UUID
from uuid_utils import uuid7

@dataclass
class OrderAggregate:
    """Aggregate root with invariant enforcement."""

    id: UUID = field(default_factory=uuid7)
    customer_id: UUID  # Reference by ID, not Customer object!
    _items: list["OrderItem"] = field(default_factory=list)
    status: str = "draft"

    MAX_ITEMS = 100

    def add_item(self, product_id: UUID, quantity: int, price: Money) -> None:
        """Add item with invariant checks."""
        self._ensure_modifiable()
        if len(self._items) >= self.MAX_ITEMS:
            raise DomainError("Max items exceeded")
        self._items.append(OrderItem(product_id, quantity, price))

    def _ensure_modifiable(self) -> None:
        if self.status != "draft":
            raise DomainError(f"Cannot modify {self.status} order")
```

See [aggregate-root-template.py](scripts/aggregate-root-template.py) for complete implementation.

## Key Decisions

| Decision | Recommendation |
|----------|----------------|
| Aggregate size | Small (< 20 children), split if larger |
| Cross-aggregate refs | Always by ID, never by object |
| Consistency | Immediate within, eventual across |
| Events | Collect in root, publish after persist |

See [aggregate-sizing.md](references/aggregate-sizing.md) for sizing guidelines.

## Anti-Patterns (FORBIDDEN)

```python
# NEVER reference aggregates by object
customer: Customer  # WRONG → customer_id: UUID

# NEVER modify multiple aggregates in one transaction
order.submit()
inventory.reserve(items)  # WRONG - use domain events

# NEVER expose mutable collections
def items(self) -> list:
    return self._items  # WRONG → return tuple(self._items)

# NEVER have unbounded collections
orders: list[Order]  # WRONG - grows unbounded
```

## Related Skills

- `domain-driven-design` - DDD building blocks (entities, VOs)
- `distributed-locks` - Cross-aggregate coordination
- `idempotency-patterns` - Safe retries

## References

- [Aggregate Sizing](references/aggregate-sizing.md) - When to split
- [Invariant Enforcement](references/invariant-enforcement.md) - Business rules
- [Eventual Consistency](references/eventual-consistency.md) - Cross-aggregate

## Capability Details

### aggregate-root
**Keywords:** aggregate root, consistency boundary, transactional
**Solves:** Design aggregate roots, control child access, enforce boundaries

### invariants
**Keywords:** invariant, business rule, validation, specification
**Solves:** Enforce business rules, validate state, specification pattern

### aggregate-sizing
**Keywords:** aggregate size, small aggregate, performance
**Solves:** Right-size aggregates, when to split, performance trade-offs

### cross-aggregate
**Keywords:** reference by ID, eventual consistency, domain events
**Solves:** Reference other aggregates, coordinate changes, eventual consistency

Overview

This skill codifies DDD aggregate design patterns for clear consistency boundaries and invariant enforcement. It helps teams design aggregate roots, manage child entities, and decide when to split or reference other aggregates. The guidance emphasizes transactional guarantees, reference-by-ID, and performance-conscious sizing.

How this skill works

The skill describes concrete rules and templates for modeling aggregates: the root controls access, it enforces invariants, and it defines a transactional boundary so only one aggregate is modified per transaction. It prescribes referencing other aggregates by ID and collecting domain events on the root for eventual cross-aggregate coordination. Practical examples and anti-patterns show how to enforce invariants, avoid mutable exposures, and size aggregates for performance.

When to use it

  • Designing a new aggregate root and its children
  • Enforcing business invariants and transactional rules
  • Deciding whether to split a large aggregate
  • Modeling references to other domain objects across boundaries
  • Implementing domain events or eventual consistency patterns

Best practices

  • Root controls access: external code only touches the aggregate root, not internal children
  • Reference other aggregates by ID only; never hold object pointers to other aggregates
  • Keep aggregate size small (recommendation: less than ~20 children) and split when logically separate
  • Enforce invariants inside the root and prevent modifications once immutable states are reached
  • Collect domain events on the root and publish them after persisting to coordinate cross-aggregate changes

Example use cases

  • Order aggregate: root enforces total, max items, and immutability after shipping
  • Inventory reservation: publish events from order root to trigger eventual inventory updates
  • Customer service: reference customer by ID from orders to avoid transactional coupling
  • Large collections: split order line items into separate aggregates if child count or lifecycle diverges
  • Cross-aggregate workflows: use events and distributed locks instead of multi-aggregate transactions

FAQ

Why reference by ID instead of object?

Referencing by ID keeps aggregates autonomous, prevents accidental cross-aggregate transactions, and reduces coupling and memory footprint.

When should I split an aggregate?

Split when a root grows beyond a small number of children, when children have independent lifecycles, or when update hotspots cause contention; aim for aggregates that are cheap to load and modify.