Files
freemo d9e5668cec fix(skills): comprehensive final audit pass for programming-patterns skill
Fixes and improvements from exhaustive audit:

Consistency fixes in SKILL.md:
- 'Pipe & Filter' → 'Pipe and Filter' (one stray '&' found and corrected)
- 'Singleton for factory instance' → clarified to 'register factory as
  singleton-scoped via DI container' (less misleading wording)
- Documentation Format section updated with note that SKILL.md itself is the
  authoritative source for related-pattern combinations

Coverage fix — Related Patterns sections:
- Added '## Related Patterns' to ALL 94 pattern files (was 0/94)
- Each section lists 3–6 related patterns with relationship descriptions
- Covers: why they're related, when to prefer one vs the other,
  and which are often confused

SOLID principles → Creational → Structural → Behavioral → Architectural →
Concurrency → Functional → Resilience → Data Access → Messaging →
Testing → Error Handling → Microservice — all 13 categories covered

Code verification:
- Python: 0 failures (all 85 testable blocks pass)
- Go: 0 failures (all 76 testable blocks pass)
- JavaScript: 0 failures (all 78 testable blocks pass)
- All 239 code blocks verified correct after edits

Final skill state:
- 108 files, 36,524 lines across 13 reference categories
- 94/94 pattern files have Related Patterns sections
- 2,815-line SKILL.md with 67 decision trees, 23 scenarios,
  0 broken references, 0 naming inconsistencies
2026-04-15 13:22:13 -04:00

9.1 KiB

Aggregate Pattern

Problem

In a complex domain, multiple related objects must be kept consistent. An Order with LineItems must maintain invariants like "total equals sum of line items" and "can't remove the last item." Without clear boundaries, any code can modify any object, leading to inconsistent state and hard-to-find bugs.

Solution

Define an aggregate — a cluster of related domain objects treated as a single unit for data changes. The aggregate has:

  • Aggregate root — The single entry point. All external access goes through the root.
  • Invariants — Business rules the aggregate enforces internally.
  • Transactional boundary — The entire aggregate is loaded and saved as one unit.

External code never reaches inside the aggregate to modify child objects directly.

When to Use

  • Domain-Driven Design with complex domain models
  • When multiple objects must maintain consistency invariants
  • When you need clear transactional boundaries
  • Order + LineItems, Account + Transactions, Cart + Items

When to Avoid

  • Simple CRUD without business invariants
  • Read-heavy systems where aggregates add unnecessary indirection
  • When objects don't have meaningful consistency rules
  • Reporting/analytics queries (use read models instead)

Pseudocode

class Order (Aggregate Root):
    items: list[LineItem]

    function add_item(product, quantity, price):
        enforce invariants
        items.add(LineItem(product, quantity, price))
        recalculate_total()

    function remove_item(product):
        enforce: items.length > 1
        items.remove_by(product)
        recalculate_total()

    # External code cannot directly modify items

Python Implementation

from dataclasses import dataclass, field
from typing import Optional


@dataclass(frozen=True)
class Money:
    amount: float
    currency: str = "USD"

    def add(self, other: "Money") -> "Money":
        return Money(round(self.amount + other.amount, 2), self.currency)

    def multiply(self, n: int) -> "Money":
        return Money(round(self.amount * n, 2), self.currency)

    def __str__(self):
        return f"${self.amount:.2f}"


@dataclass
class LineItem:
    product: str
    quantity: int
    unit_price: Money

    @property
    def subtotal(self) -> Money:
        return self.unit_price.multiply(self.quantity)

    def __repr__(self):
        return f"LineItem({self.product!r}, qty={self.quantity}, subtotal={self.subtotal})"


class OrderError(Exception):
    pass


class Order:
    """Aggregate root — all modifications go through this class."""

    def __init__(self, order_id: str, customer: str):
        self.id = order_id
        self.customer = customer
        self._items: list[LineItem] = []
        self._status = "draft"

    def add_item(self, product: str, quantity: int, unit_price: Money) -> None:
        if self._status != "draft":
            raise OrderError(f"Cannot modify order in '{self._status}' status")
        if quantity <= 0:
            raise OrderError("Quantity must be positive")

        # Check if product already exists
        for item in self._items:
            if item.product == product:
                item.quantity += quantity
                return

        self._items.append(LineItem(product, quantity, unit_price))

    def remove_item(self, product: str) -> None:
        if self._status != "draft":
            raise OrderError(f"Cannot modify order in '{self._status}' status")

        matching = [i for i in self._items if i.product == product]
        if not matching:
            raise OrderError(f"Product '{product}' not in order")

        self._items = [i for i in self._items if i.product != product]

    def place(self) -> None:
        if not self._items:
            raise OrderError("Cannot place empty order")
        if self._status != "draft":
            raise OrderError(f"Cannot place order in '{self._status}' status")
        self._status = "placed"

    @property
    def total(self) -> Money:
        result = Money(0)
        for item in self._items:
            result = result.add(item.subtotal)
        return result

    @property
    def item_count(self) -> int:
        return len(self._items)

    @property
    def status(self) -> str:
        return self._status

    @property
    def items(self) -> tuple:
        """Read-only view of items."""
        return tuple(self._items)

    def __repr__(self):
        return (f"Order(id={self.id!r}, customer={self.customer!r}, "
                f"items={self.item_count}, total={self.total}, status={self._status!r})")


# --- Demo ---
print("=== Build order through aggregate root ===")
order = Order("ORD-001", "Alice")
order.add_item("Widget", 2, Money(9.99))
order.add_item("Gadget", 1, Money(24.99))
print(f"  {order}")
print(f"  Items: {order.items}")

print()
print("=== Aggregate enforces invariants ===")

# Add more of existing product (aggregate merges)
order.add_item("Widget", 3, Money(9.99))
print(f"  After adding 3 more Widgets: qty is now {order.items[0].quantity}")
print(f"  Total: {order.total}")

print()
print("=== Place order ===")
order.place()
print(f"  Status: {order.status}")

# Try to modify placed order
try:
    order.add_item("Doohickey", 1, Money(5.00))
except OrderError as e:
    print(f"  Modification blocked: {e}")

print()
print("=== Invariant: can't place empty order ===")
empty = Order("ORD-002", "Bob")
try:
    empty.place()
except OrderError as e:
    print(f"  Blocked: {e}")

print()
print("=== Invariant: quantity must be positive ===")
draft = Order("ORD-003", "Charlie")
try:
    draft.add_item("Thing", -1, Money(10.00))
except OrderError as e:
    print(f"  Blocked: {e}")

Output:

=== Build order through aggregate root ===
  Order(id='ORD-001', customer='Alice', items=2, total=$44.97, status='draft')
  Items: (LineItem('Widget', qty=2, subtotal=$19.98), LineItem('Gadget', qty=1, subtotal=$24.99))

=== Aggregate enforces invariants ===
  After adding 3 more Widgets: qty is now 5
  Total: $74.94

=== Place order ===
  Status: placed
  Modification blocked: Cannot modify order in 'placed' status

=== Invariant: can't place empty order ===
  Blocked: Cannot place empty order

=== Invariant: quantity must be positive ===
  Blocked: Quantity must be positive

JavaScript Implementation

class Money {
  constructor(amount, currency = "USD") {
    this.amount = Math.round(amount * 100) / 100;
    this.currency = currency;
    Object.freeze(this);
  }
  add(other) { return new Money(this.amount + other.amount, this.currency); }
  multiply(n) { return new Money(this.amount * n, this.currency); }
  toString() { return `$${this.amount.toFixed(2)}`; }
}

class LineItem {
  constructor(product, quantity, unitPrice) {
    this.product = product;
    this.quantity = quantity;
    this.unitPrice = unitPrice;
  }
  get subtotal() { return this.unitPrice.multiply(this.quantity); }
  toString() { return `LineItem(${this.product}, qty=${this.quantity}, subtotal=${this.subtotal})`; }
}

class OrderError extends Error {}

class Order {
  #items = [];
  #status = "draft";

  constructor(id, customer) {
    this.id = id;
    this.customer = customer;
  }

  addItem(product, quantity, unitPrice) {
    if (this.#status !== "draft") throw new OrderError(`Cannot modify '${this.#status}' order`);
    if (quantity <= 0) throw new OrderError("Quantity must be positive");
    const existing = this.#items.find((i) => i.product === product);
    if (existing) { existing.quantity += quantity; return; }
    this.#items.push(new LineItem(product, quantity, unitPrice));
  }

  place() {
    if (this.#items.length === 0) throw new OrderError("Cannot place empty order");
    if (this.#status !== "draft") throw new OrderError(`Cannot place '${this.#status}' order`);
    this.#status = "placed";
  }

  get total() {
    return this.#items.reduce((sum, i) => sum.add(i.subtotal), new Money(0));
  }

  get status() { return this.#status; }
  get items() { return [...this.#items]; }

  toString() {
    return `Order(id=${this.id}, items=${this.#items.length}, total=${this.total}, status=${this.#status})`;
  }
}

// --- Demo ---
console.log("=== Build order ===");
const order = new Order("ORD-001", "Alice");
order.addItem("Widget", 2, new Money(9.99));
order.addItem("Gadget", 1, new Money(24.99));
console.log(`  ${order}`);

order.addItem("Widget", 3, new Money(9.99));
console.log(`  After adding 3 more Widgets: total=${order.total}`);

order.place();
console.log(`  Status: ${order.status}`);

try { order.addItem("X", 1, new Money(1)); }
catch (e) { console.log(`  Blocked: ${e.message}`); }

console.log("\n=== Empty order ===");
try { new Order("ORD-002", "Bob").place(); }
catch (e) { console.log(`  Blocked: ${e.message}`); }
  • Repository — repositories load and store entire aggregates as atomic units; no partial aggregate loading.
  • Value Object — aggregates are composed of entities and value objects; value objects carry the immutable attributes.
  • Guard Clause — aggregates enforce invariants using guard clauses inside their mutating methods.
  • Domain Events (Observer) — aggregates raise domain events (observer pattern) when significant state changes occur.