Skip to content
This repository was archived by the owner on Oct 7, 2026. It is now read-only.

docs(aggregate): draw ownership, reference and snapshot boundaries - #151

Merged
btravers merged 1 commit into
mainfrom
docs/137-aggregate-boundaries
Oct 6, 2026
Merged

btravers merged 1 commit into
mainfrom
docs/137-aggregate-boundaries

Conversation

@btravers

@btravers btravers commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Closes #137

Summary

  • Reworks docs/how-to/model-an-aggregate.md around an Order that owns its OrderLines, references a separate Customer by ID, and snapshots the billing party (billTo) when placed.
  • Adds a table of what each kind of part is (owned child entity, value object, aggregate reference, historical snapshot), with the test for what belongs inside the boundary. Being nested is not that test.
  • Shows a root operation (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.
  • States what commits atomically (order, lines and snapshot together; Customer separately) and why immutable instances do not prevent lost updates or a stored state no instance ever passed. Links Persistence story: relational mapping guidance and a migration hook #37 for the mapping/concurrency recipe and Identity comparison alongside structural equals() #38 for identity comparison.
  • New section separating Entity.abstract (inheritance root) from a DDD aggregate root. No AggregateRoot base class is added.
  • Union, matching and abstract-root sections are kept. #model-a-union-of-entities is unchanged, since tags-and-identity.md links to it.
  • Backing code: new examples/billing-domain/src/order.ts + order.spec.ts (8 tests), exported as ./order. Its emitted order.d.ts is added to the 5.9.3 emitted-declaration check.

Finding

Exporting Order.factory(...) directly fails declaration emit with TS4023 ($brand cannot 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 typed openOrder function 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 green
  • pnpm --filter ./docs build uncached
  • No changeset: packages/entity is untouched

🤖 Generated with Claude Code

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>
Copilot AI balanced review requested due to automatic review settings October 6, 2026 08:20
@coderabbitai

coderabbitai Bot commented Oct 6, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You'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.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: btravstack/entity/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1e15fbc0-fd70-4066-9e0b-9880cd523d2c
📥 Commits

Reviewing files that changed from the base of the PR and between 4039194 and fcf8f19.

📒 Files selected for processing (4)
  • docs/how-to/model-an-aggregate.md
  • examples/billing-domain/package.json
  • examples/billing-domain/src/order.spec.ts
  • examples/billing-domain/src/order.ts
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.md around 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) and order.spec.ts (8 tests).
  • Exports ./order and extends the typecheck script to type-check the emitted order.d.ts under 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.

@btravers
btravers merged commit 8674834 into main Oct 6, 2026
15 checks passed
@btravers
btravers deleted the docs/137-aggregate-boundaries branch October 6, 2026 08:36
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Clarify aggregate ownership, snapshots, and aggregate roots in the nesting guide

2 participants