Repository navigation
docs(aggregate): draw ownership, reference and snapshot boundaries - #151
Conversation
Rework the model-an-aggregate guide around an Order that owns its lines, references a separate Customer by ID and snapshots the billing party when placed. Distinguish owned child entities, value objects, aggregate references and historical snapshots; show a root operation revalidating aggregate-wide invariants; state what commits atomically and why immutability does not stop concurrent writes; and separate Entity.abstract from a DDD aggregate root. Back it with a tested example, examples/billing-domain/src/order.ts, exported as ./order and included in the 5.9.3 emitted-declaration check. Closes #137 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 34 minutes. View limit detailsLimit details: You’ve used the included review currently available. Review configuration: ⚙️ Run configuration
📒 Files selected for processing (4)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The change is docs-and-example-only with no modifications to the published package, and verification of imports, types, numeric consistency, links, the emit-check wiring, and conventions surfaced no defects.
Review effort: Balanced
Findings: None
What changed in this PR
This PR reworks the "Model an aggregate" how-to guide (resolving issue #137) to teach DDD aggregate boundaries rather than mere entity nesting, and adds a runnable, tested Order example in examples/billing-domain as its backing code. It distinguishes owned child entities (lines), value objects (Money, BillingParty), aggregate references (customerId), and historical snapshots (billTo), shows a root operation (changeQuantity) that revalidates aggregate-wide invariants, explains atomic commit boundaries and why immutability alone doesn't prevent lost updates, and separates an Entity.abstract inheritance root from a DDD aggregate root. It touches only docs/ and examples/, leaving the published packages/entity untouched (no changeset).
Changes:
- Rewrites
docs/how-to/model-an-aggregate.mdaround ownership/reference/snapshot distinctions, linking issues #37 and #38 and keeping the union/matching sections. - Adds
examples/billing-domain/src/order.ts(Order/OrderLine/Customer, factories, modelled errors) andorder.spec.ts(8 tests). - Exports
./orderand extends the typecheck script to type-check the emittedorder.d.tsunder TS 5.9.3.
| File | Description |
|---|---|
docs/how-to/model-an-aggregate.md |
Reworked guide drawing ownership, reference and snapshot boundaries; renamed/added sections; updated snippets to the Order example. |
examples/billing-domain/src/order.ts |
New backing example: owned lines, aggregate reference, billing snapshot, root operations, and module-private factory behind openOrder. |
examples/billing-domain/src/order.spec.ts |
8 tests covering references/ownership, root-level revalidation, snapshotting, and rehydration invariants. |
examples/billing-domain/package.json |
Adds ./order export and appends order.d.ts to the 5.9.3 emitted-declaration check. |
I verified: all vocabulary imports (Currency, DisplayName, LineLabel, Money) resolve; the TaggedError payload/override message pattern matches the established errors.ts convention; crypto.randomUUID() and factory patterns match index.ts; nested-entity field typing makes sum(d.lines)/line.subtotal valid in both computed and invariant contexts (since InputOf resolves a nested entity field to its instance type including computed fields); numeric consistency (€10,000 ceiling vs minor units) holds across code, doc and tests; the order.d.ts emit-check addition is correct and necessary (the file isn't imported by any already-named declaration, per emit-guards.ts); and no cross-references to renamed/removed anchors were broken (#model-a-union-of-entities is unchanged, and /how-to/persist-and-rehydrate and #an-entity-is-final targets exist).
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Closes #137
Summary
docs/how-to/model-an-aggregate.mdaround anOrderthat owns itsOrderLines, references a separateCustomerby ID, and snapshots the billing party (billTo) when placed.changeQuantity) that changes an owned line and re-runs the order-wide invariants. A quantity can be valid for the line and still push the order over its ceiling.Entity.abstract(inheritance root) from a DDD aggregate root. NoAggregateRootbase class is added.#model-a-union-of-entitiesis unchanged, sincetags-and-identity.mdlinks to it.examples/billing-domain/src/order.ts+order.spec.ts(8 tests), exported as./order. Its emittedorder.d.tsis added to the 5.9.3 emitted-declaration check.Finding
Exporting
Order.factory(...)directly fails declaration emit with TS4023 ($brandcannot be named) on TS 7.0.2. This happens whenever the entity has a nested-entity field. The example keeps that factory module-private behind a typedopenOrderfunction and documents why. This may deserve its own issue.Test plan
pnpm format --check && pnpm lint && pnpm typecheck && pnpm test && pnpm knip && pnpm build, all greenpnpm --filter ./docs builduncachedpackages/entityis untouched🤖 Generated with Claude Code