# Why Domain-Driven Design Is a Great Fit for Coding with Claude

> Source: <https://menelaos.vergis.net/posts/Why-Domain-Driven-Design-Is-a-Great-Fit-for-Coding-with-Claude>
> Published: 2026-07-16 17:01:18+00:00

## Why Domain-Driven Design Is a Great Fit for Coding with Claude

### If an LLM is only as good as the context you give it, then the discipline that produces the best context wins. That discipline is Domain-Driven Design

Jul 14, 2026

**TL;DR**

- An LLM's output is bounded by the quality of the context it receives. DDD is a discipline for producing
**explicit, named, unambiguous context**. - The artifacts DDD makes you create, such as a shared vocabulary, clear boundaries, invariants stated out loud, are exactly what an AI coding assistant is starving for. The glossary you'd write for a human teammate
*is*a great system prompt. - The catch: an LLM left to its own defaults produces the
*opposite*of DDD; anemic CRUD, generic names, blurred boundaries, over-engineered patterns. You have to steer. - Use the cheap parts of DDD (language + behavior-rich models) everywhere; adopt the expensive parts only when the domain earns them.

## What DDD actually is (the 60-second version)

Domain-Driven Design isn't a framework or a folder layout. It's a bet: **in complex software, the hard part is understanding the domain, not the technology.** So you put a *model* of the domain at the center: a deliberately simplified, purpose-built view. You express it in a language shared by developers and domain experts, and you make the code mirror that model directly.

It has two halves. **Strategic design** is the big-picture stuff: a shared vocabulary (the *Ubiquitous Language*), and *bounded contexts*, the boundaries within which a model and its language stay consistent. **Tactical design** is the building blocks inside a boundary: entities, value objects, aggregates, domain events.

One pitfall to watch for, and the one .NET developers hit most: the **anemic model**. It's an object that's just a bag of public getters and setters, with all the real rules exiled to separate "service" classes. DDD's instinct runs the other way: push behavior into the object that owns the data, so its rules are guarded where they live.

That's the whole foundation. Now here's why it pairs so well with an AI.

## Why DDD fits LLM-assisted development

An LLM's output quality is bounded by the quality of the context it's given. DDD is a discipline for manufacturing exactly that kind of context.Every DDD artifact doubles as prompt material.

A quick example to make this concrete, and one I'll reuse throughout. Imagine a live-event ticketing app. The team agrees on precise words: a **hold** is a temporary, unpaid grab on some seats; a **booking** is what that hold becomes once it has been paid for; and an unpaid hold **expires** after a few minutes so the seats go back on sale. Three plain words, used the same way by everyone. Keep them in mind, because every point below leans on them.

Four concrete ways this shows up:

**1. Ubiquitous Language ↔ prompt context.** The single biggest lever on AI code quality is unambiguous vocabulary. Because the team fixed those words, the model generates `SeatHold.Expire()`

instead of guessing at `Reservation.Update()`

. The word "expire" was chosen deliberately over "cancel," so nobody, human or machine, has to guess which verb means what. The glossary you write for the team is the same glossary the AI needs. You were going to write it for humans anyway, so now it pays double.

**2. Bounded contexts ↔ scoping the session.** LLMs degrade when you dump an entire system into the prompt; attention spreads thin and boundaries blur. A bounded context is a natural unit of work: "we're in the Ticketing context today; here's its language and its rules; ignore Payments." That maps cleanly onto one focused session or one subagent, which is exactly how you get the model to stay on-target.

**3. Behavior-rich models ↔ code the AI can't silently corrupt.** When invariants live *inside* the object (`SeatHold.Confirm()`

checks "must be active, must not be expired"), there's one guarded doorway. When they're scattered across services (the anemic style), any generated line can reach in and set `Status = "Confirmed"`

, skipping every rule. Concentrating rules in one place shrinks the surface where an AI (or any teammate) can quietly break things.

**4. Tests-as-spec ↔ an executable contract.** DDD's invariants translate directly into tests: *"cannot confirm an expired hold."* Have the assistant write those tests first, and you've given it an executable definition of "correct" to code against, which is far more reliable than prose alone.

The through-line: **DDD forces you to make the domain explicit, and explicit is precisely what a language model can act on.**

## The traps hiding in Claude + DDD

Here's the honest part. An LLM's *default* behavior is the enemy of good DDD, because the training data is full of the patterns DDD warns against. Know these before you start:

**It defaults to anemic CRUD.** Ask for "a SeatHold class" cold and you'll often get public getters/setters plus a`SeatHoldService`

holding the logic, the exact anti-pattern. You have to explicitly ask for behavior on the object and private setters.**It invents vocabulary.** You say "hold," it writes`Reservation`

. It's plausible, it compiles, and now your code and your team speak different languages. This drift is silent and compounds.**It reaches across boundaries.** Left unscoped, the model will happily wire Ticketing directly into Payments and grow a god-object, because it doesn't feel the seam you drew.**It over-engineers.** Aggregates, repositories, domain-event buses for a three-table admin panel. The patterns are in the training set, so it applies them whether or not the domain warrants it. DDD without the "when not to" is just ceremony.**It generates plausible-but-wrong invariants.** You say a hold expires after 10 minutes; the generated code says 15. The number looks reasonable, the code compiles, and nothing complains. This is the most dangerous trap on the list, because a wrong*rule*hides inside perfectly correct*syntax*, so a review that only checks whether the code runs sails right past it. The defense is DDD's own habit: pin the invariant down in the glossary and in a test, so "10 minutes" is something the code is checked against, not something the model was left to guess.**Context goes stale.** Over a long session the model drifts from the agreed language. Re-ground it.

None of these are reasons not to use Claude for DDD. They're reasons to steer, which is what the workflow is for.

## A practical workflow for LLM-driven DDD

**Put the Ubiquitous Language in a**(or your assistant's standing-instructions file). List each term, its precise meaning, and a "not to be confused with." Now the language is auto-loaded as context on`CLAUDE.md`

*every*session, and the glossary becomes the AI's default vocabulary instead of something you re-explain each time.**Work one bounded context at a time, and organize it.** Give each context its own file for its language and rules (`docs/contexts/ticketing.md`

), or better, a`CLAUDE.md`

inside that context's folder so it loads automatically the moment you work there. Structuring the codebase by context (a folder per context) makes this fall out for free. One context per session or subagent keeps the model's attention where the seam is.**Make it restate the model before it codes.** Ask:*"Before writing anything, list the invariants*`SeatHold`

*must enforce, in your own words."*This is teach-back in reverse: drift and misunderstandings surface*before*they become code, when they're cheap to fix.**Bake the behavior-rich preference into standing instructions.** Put it where it persists: a coding-standards line in`CLAUDE.md`

, or a reusable skill. Something like: "domain objects that enforce rules should keep those rules on the object and expose intent-revealing methods like`Confirm()`

and`Expire()`

rather than open setters." Keep it a preference, not a mandate. It applies to the objects guarding real invariants, not to everything else. Value objects, DTOs at the boundary, and simple config carriers are perfectly fine with plain properties. Forcing every setter private across the whole codebase just swaps one anti-pattern for ceremony.**Tests first, as the contract.** Have it write the invariant tests (`Cannot_confirm_an_expired_hold`

) before the implementation. You review the*spec*, which is small and readable, rather than hunting bugs in the implementation later.**Review against the language, not just correctness.** In review, ask two questions: does it work,*and*does it speak the Ubiquitous Language? A method named`Process()`

is a smell even if it passes, because it means a concept wasn't named.

The loop is virtuous: a sharper model makes sharper prompts, sharper prompts make better code, and the code you read back teaches you the model. Human, domain expert, and AI all reading one language.

## Drawing the line: which parts of DDD to actually use

DDD is not all-or-nothing, and this is the part beginners (and eager LLMs) get wrong most often. The useful frame is **cheap vs. expensive**.

**Cheap, use nearly everywhere:**

**Ubiquitous Language.** Costs only discipline. Pure upside, even on a small project. And with an AI, it directly improves generated code.**Behavior-rich objects (no anemic models).** This is just good object-oriented design; it's*simpler*than data-bag-plus-service, not more complex.**A few value objects** for the obvious things like`Money`

and`SeatNumber`

. Cheap in C# with records, and they delete whole classes of bugs.

**Expensive, adopt only when the domain earns it:**

- Carefully designed
**aggregates** and consistency boundaries. **Context maps** across multiple teams and contexts.**Repositories, separate application/domain layers, domain-event infrastructure.**

The deciding factor isn't how long the project takes. It's **how rich the rules are**. A two-day feature encoding god-inspired pricing rules may want value objects and behavior-rich models; a three-month CRUD panel may need almost none of it. Tell your assistant where the line is, or it will scaffold the whole cathedral for a garden shed.

## The bottom line

LLMs reward whoever gives them the clearest picture of the problem. DDD is a decades-old discipline for producing exactly that picture: a named language, drawn boundaries, rules stated out loud. Pairing them isn't a hack; it's two things that want the same input.

Give the model a Ubiquitous Language and a bounded context, insist on behavior where the data lives, verify the rules, and draw the line at the domain's actual complexity. Do that, and Claude stops guessing at your domain and starts speaking it.
