Domain-Driven Design with AI Assistance
Six months into the project, “Order” means three different things across the codebase: a row in orders, a DTO in the checkout service, and a React view-model. The AI keeps inventing fields that don’t exist on the real entity, every refactor quietly reintroduces persistence concerns into the domain layer, and the term your domain experts use in meetings appears nowhere in the code. The model and the language have drifted apart.
Domain-Driven Design fixes that drift by anchoring the code to a shared vocabulary, the Ubiquitous Language. The catch is that DDD demands discipline an AI assistant will happily ignore unless you make the rules explicit and enforce them. Done right, the AI becomes a custodian of your domain knowledge instead of the thing eroding it.
What you’ll walk away with
Section titled “What you’ll walk away with”- Context-file setup for each tool so the AI speaks your domain’s language by default
- A real round-trip: prompt → generated TypeScript aggregate → how you check it against an invariant
- Copy-paste prompts to model an aggregate, generate the implementation, and govern bounded-context boundaries
- The failure modes that show up when DDD meets AI, and how to catch them
Step 1: Encode the ubiquitous language
Section titled “Step 1: Encode the ubiquitous language”The foundation of DDD is the Ubiquitous Language, a vocabulary shared by developers and domain experts. The single highest-leverage DDD move with an AI assistant is to put that vocabulary in the context file the agent reads on every request, so it stops guessing.
The file content is the same idea across tools; only the filename and location differ.
Cursor reads project rules from .cursor/rules/*.mdc. Create .cursor/rules/domain.mdc and set it to always apply so every agent turn in the repo sees the glossary.
Claude Code reads CLAUDE.md from the project root (and nested directories) on every session. Put the glossary there, or in a CLAUDE.md inside the domain module so it loads only when you’re working in that context.
Codex reads AGENTS.md from the project root and from directories it works in. Put the same glossary in AGENTS.md so App, CLI, and Cloud runs all share it.
The content of that file, regardless of tool, is the actual domain glossary, so a fenced Markdown block is correct here (it is file content, not a prompt):
# Ubiquitous Language — Order Context
- **Order**: a customer's request to purchase products. Aggregate Root.- **LineItem**: an entry in an Order for a product + quantity. Value Object (immutable).- **OrderStatus**: state of an Order — Pending | Paid | Shipped | Delivered | Cancelled. Enum.- **Customer**: the person who placed the Order. Referenced by `CustomerId`, never embedded.
Invariants:- An Order has at most 10 unique LineItems.- Status transitions are one-way along the list above (no Shipped → Pending).- Total is derived from LineItems; never set directly.Step 2: Model the aggregate, then check the invariant
Section titled “Step 2: Model the aggregate, then check the invariant”Now ask the AI to design the Order aggregate against that language, and immediately verify it enforces the invariant rather than just compiling.
-
Ask for the model. Reference the language explicitly so the AI uses your terms, not its defaults.
-
Read what it generated. A correct
addItemrejects the 11th distinct product and recomputestotal;LineItemhas no setters. If the AI exposed a publicsetTotalor let status jump backward, the model leaked.addItem(productId: ProductId, qty: number): void {const distinct = new Set(this.items.map(i => i.productId.value));if (!distinct.has(productId.value) && distinct.size >= 10) {throw new OrderInvariantError('An order cannot exceed 10 line items');}// ...merge or push, then recompute derived total} -
Pin the invariant with a test. Don’t trust prose, ask for the failing-case test so the invariant can’t silently regress later.
Step 3: Govern the bounded contexts
Section titled “Step 3: Govern the bounded contexts”The other place AI helps is ongoing architectural governance, catching when one bounded context reaches into another’s internals instead of communicating through events or an anti-corruption layer. This is where the workflow diverges by tool.
Use agent mode for an interactive review while you have the files open. Point it at the boundary you’re worried about.
Run the same governance check headless in CI so a boundary violation fails the build, not just a code review.
Wire it as a Codex automation or a review on PRs so the check runs on a schedule or against every change in the cloud.
When this breaks
Section titled “When this breaks”What’s next
Section titled “What’s next”- Behavior-Driven Development — turn the ubiquitous language into executable scenarios
- Test-Driven Development — the discipline that pins your invariants
- Agile Workflows — how the model evolves sprint over sprint