| name | ddd-workflow |
| description | Domain-driven design discipline — bounded contexts, ubiquitous language, aggregates, domain events, context maps, and how the .planning/DOMAIN.md file drives code structure and naming. Use whenever the user is modelling a new domain, extracting domain concepts from existing code, deciding where code should live, or naming things. |
DDD Workflow
Draht embeds domain-driven design into project initialization, planning, and execution. The .planning/DOMAIN.md file is the single source of truth for domain concepts.
.planning/DOMAIN.md Structure
## Bounded Contexts
- **Billing** — everything about invoices, payments, subscriptions
- **Catalog** — products, pricing, availability
- **Fulfillment** — order processing, shipping, returns
## Ubiquitous Language
- **Invoice** — a document requesting payment for delivered goods or services
- **Order** — a customer's request to purchase goods, before fulfillment
- **Line Item** — a single row on an invoice or order
- **SKU** — a unique identifier for a product variant in the catalog
## Context Map
- Billing ← Catalog (downstream — billing reads product info)
- Fulfillment ← Billing (downstream — fulfillment needs invoice status)
- Shared kernel: Money, TaxRate (used by Billing and Fulfillment)
## Aggregates
### Billing
- Invoice (root) — LineItem, Payment
- Subscription (root) — BillingCycle
### Catalog
- Product (root) — Variant, Price
## Domain Events
- `InvoiceIssued` — Billing → Fulfillment, Notification
- `PaymentReceived` — Billing → Notification
- `OrderShipped` — Fulfillment → Notification, Customer
The Five Rules
1. Bounded contexts shape the code
- File/module structure mirrors bounded contexts:
src/billing/, src/catalog/, src/fulfillment/
- Each context owns its aggregates, value objects, services, and domain events
- Cross-context imports are suspicious — prefer domain events or ACL adapters
2. Code uses the ubiquitous language
- Class names, method names, variable names must match the glossary
- If you need a new term, update
DOMAIN.md first, then write the code
- Never invent terms in code that aren't in the glossary
3. Aggregates enforce invariants
- Each aggregate has one root entity
- All writes go through the root — never modify child entities directly from outside
- Aggregate boundaries align with transaction boundaries
- Aggregates reference each other by ID, not by reference
4. Domain events cross context boundaries
- Upstream context publishes an event (
InvoiceIssued)
- Downstream contexts subscribe and react (Notification sends email, Fulfillment releases order)
- No direct function call from Billing into Fulfillment — always via event
5. Shared kernel is explicit
- If two contexts must share a type (e.g.
Money, TaxRate), put it in src/shared/ and document it in the Context Map
- Shared kernel changes are high-cost — they affect multiple contexts
- Prefer duplication over coupling when in doubt
The Post-Phase Domain Health Check
The gsd-post-phase.cjs hook checks DOMAIN.md after each phase:
- Is
## Bounded Contexts section present?
- Is
## Ubiquitous Language section present?
- Count of unique PascalCase terms (proxy for glossary size)
The gsd-quality-gate.cjs script also runs a domain validator that compares identifiers in code against the glossary and flags unknown terms.
Drift check: after each phase, run draht-tools graph-clusters and compare the map's clusters/contexts against DOMAIN.md — a new cluster or a cluster spanning two contexts means the code drifted from the model. Reconcile DOMAIN.md first, then the code.
Extracting Domain from Existing Code
When running /init-project or /map-codebase on a codebase that wasn't built with DDD:
- Surface CANDIDATE contexts from the map:
draht-tools graph-clusters for code groupings + draht-tools graph-context <dir> to orient each. Clusters are STRUCTURAL (import topology), not semantic — confirm with a human before equating a cluster with a bounded context.
- List top-level
src/ subdirectories — candidates for bounded contexts
- Scan PascalCase class / interface / type names — seed from
modules[*].symbols / GRAPH_REPORT.md instead of grepping — candidates for entities and value objects
- Scan repeated nouns in function names — candidates for domain concepts
- Look for cross-directory imports — candidates for context coupling to fix
- Write
DOMAIN.md with what you found + what should exist
- Use subsequent phases to refactor toward the target model
Anti-patterns
Anemic domain model — entities that are just data bags with no behaviour. Push logic into the entities.
Scattered aggregates — one aggregate's logic spread across multiple contexts. Consolidate or introduce an ACL.
Terminology drift — the same concept called different things in different files. Fix in DOMAIN.md first, rename code second.
Shared database — multiple contexts writing to the same tables without explicit shared-kernel agreement. Break the coupling.
Direct cross-context imports — import { ... } from '../billing/...' in src/fulfillment/. Use domain events or ACL adapters.
When to Update DOMAIN.md
- Before writing code that introduces a new term → add it to the glossary first
- During
/discuss-phase when gray areas reveal missing concepts
- After
/verify-work when the reviewer agent flags domain language drift
- Whenever a refactor reveals that existing names don't match reality