It’s very trendy to hate on Domain Driven Design (DDD). So trendy in fact, that disdain for DDD has united disjoint sets of overly opinionated developers. “Cracked” engineers don’t like it because all that squishy stuff about language and behavior adds noise to their data structures. “Systems Unicorns” hate it because all those a-words (abstractions) hurt data-alignment. The “Vibe Coders” never got it. Who needs that boomer talk when code is disposable? Claude will just write the whole thing over anyways.
The zeitgeist has spoken. DDD is boring. It’s old, it’s slow. It has ED.
The Simplicity Gospel
All of this disdain exists within a backdrop of highly optimized engagement bait tuned precisely to stimulate the pleasure centers of overly online developers. Influencers ...
It’s very trendy to hate on Domain Driven Design (DDD). So trendy in fact, that disdain for DDD has united disjoint sets of overly opinionated developers. “Cracked” engineers don’t like it because all that squishy stuff about language and behavior adds noise to their data structures. “Systems Unicorns” hate it because all those a-words (abstractions) hurt data-alignment. The “Vibe Coders” never got it. Who needs that boomer talk when code is disposable? Claude will just write the whole thing over anyways.
The zeitgeist has spoken. DDD is boring. It’s old, it’s slow. It has ED.
The Simplicity Gospel
All of this disdain exists within a backdrop of highly optimized engagement bait tuned precisely to stimulate the pleasure centers of overly online developers. Influencers (and even ostensibly serious people) will clear their throats on Twitter and loudly proclaim that “everyone is over-complicating things” and hordes of random developers will join in to describe how they too love simplicity and despise how everyone else is making things too complex. These avengers of simplicity never wonder: “if we’re geniuses creating simple designs, who is making everything complex?”
Funny enough, these folks are likely to be peak enjoyers of abstractions. They use Vercel, GraphQL, Kubernetes, ORMs & the shiniest front end frameworks. They are willing to adopt others’ abstractions but not think of their own. They accept “magic” from their tools but balk at modeling their own domains. They gaze mirin at dist systems engineers & ML researchers. To them, there is no distinction between unnecessary & inherent complexity, there is only the accusation. They denounce abstraction as if it isn’t the very thing their livelihoods and industry is built on.
DDD is a wonderful strawman villain for these heroes. Mostly defenseless, often forgotten, and somehow still looming large in the anxious mental landscape of “builders who actually ship.”
Hype Fog
This is the moment in the essay, where I should “perform reasonableness” by steel-manning the anti-DDD arguments. You know…”DDD content is incredibly boring and dogmatic. DDD influencers moan about persistence ignorance and then abandon learners to figure out how to make that happen on their own. There is a lack of pragmatism & engagement with computer science fundamentals. No one talks about performance. Yes, everyone agrees simple designs are good”…etc.
But so what?
Every engineering practice gathers its own fog of war. The influencers, consultants & other denizens of the hype cycle rush in to preach the new gospel. A thousand inane posts bloom on Medium. Threadbois assemble! And the essence of the practice is lost in the froth of overwhelming persuasion. This happened for Agile, Functional Programming, Microservices, CI / CD, Orchestrators, Vector Databases, now LLMs etc. It has always been up to us to find the kernel of truth in the fog.
These engineering practices exist on many spectrums: how easy they are to grok, who they’re most useful to etc. Engineering practices that are most useful to Big Tech (Microservices, orchestrators) get a leg up because the hoi polloi can’t help but imitate success. Some practices never go mainstream, but gain small, usually rabid groups of adherents (see: Functional Programming).
| Practice | Adopters | Adoption | Core Idea |
|---|---|---|---|
| Agile | Large orgs. Then Everyone | Mainstream | Iterative development cycles. |
| TDD | High-correctness teams | Niche | Test-first development. |
| DDD | Complex-domains | Niche, Fading | Model domain, data & behavior |
| Functional Programming | Academia. Data wranglers | Niche, Mainstream Ideas | Explicit, immutable state |
| DevOps / CI/CD | Large orgs, then All Orgs | Mainstream (Tools) | Build it. Run it |
| Microservices | Large orgs. Then everyone | Mainstream | Decouple to scale |
This isn’t (only) to say that those who hate DDD don’t understand it. It’s also to say that popularity is an imperfect proxy for usefulness.
A common belief in DDD is that “all models are incorrect, but some are useful”. Engineering practices are like that, imperfect in their own way. In the end, we must find the useful parts on our own. If you don’t want to be persistence-ignorant, don’t be. Aggregates messing with your cache lines? Ditch ‘em.
Don’t wait to be spoon-fed the exact right set of tradeoffs.
Meatbag Overlords
If there’s an original sin of “simplicity gospel”, it’s denial. Pretending complexity doesn’t exist. Or treating it like some boogeyman to be feared (say it five times and it’ll ruin your elegant design). But there is no escaping complexity, it must be grappled with. Even if your domain isn’t inherently complex, just getting large groups of people to do anything is hard.
Four teams jointly own this code. No team owns this code. Every team has a customer in their database. Every team thinks their “customer” is the right one. One team’s change forces six teams to deploy their services. Downstream team hates upstream team. Nine teams are building the same thing.
And that’s just coordination…
Tax rules change mid-year. Giftcards to pay. But don’t allow refunds. This refund is actually a negative payment. This discount stacks. But not at Christmas. Time zone math ain’t math-ing. Split shipments cross a border. Make the product work for teams. Prorate this subscription across currencies. Whale customer changed domains.
And those are just requirements…
This isn’t incidental. It’s what real systems look like. The world itself is a tangle of human conventions, bureaucracies, and edge cases. Pretending otherwise doesn’t make it simple, it just makes you blind.
Bits are pure. Numbers don’t lie. But at the end of the day, what software gets built, and how it gets built is governed by the same messy, emotional, political meatbag overlords. Us.
Grappling With the World
If the real world is so messy, what can we do about it? What engineering practices can help us?
- Do microservices help? Modularity does. But how do you know how to split the whole into parts?
- DevOps? Great. But not necessarily for navigating complexity.
- Agile? Some say it creates complexity :) Functional Programming? It’s very expressive but you can’t express chaos directly. You need to tame it first. TDD? Can’t test your way out of complexity.
Most of these practices live in one world or the other — the world of bits, or the world of people. DDD is the rare one that lives in both. It doesn’t deny complexity or hide from it; it models it. It accepts the mess, maps it, and then wires that understanding straight into the software. It’s the only engineering practice that is earnestly socio-technical — as interested in how people think and speak as in how systems communicate.
And despite all the naysayers, DDD is technical:
- Bounded Contexts map beautifully to modules or Microservices.
- Aggregates map to transactional boundaries.
- Integration Events map to cross-module / cross-service data flows.
- DDD & adjacent practices generally lead to modular & maintainable code.
- DDD puts no limits on other parts of your stack. You can be as performance conscious as you want.
DDD’s obsession with clarity and consistency of language is its superpower — because the hardest part of building software isn’t code, it’s coordination, and chaos, and edge cases and people and all the other squishy bits.
You know who else thrives on clear, consistent language? LLMs.
Sisyphus, Prompt Engineer
Dropping a coding agent (or a new engineer) into a CRUD app and asking them to fix or build something is like dropping a blindfolded intern in a warehouse and asking them to fetch the “twizzlers”. It’s asking them to fish in a context desert.
Here are a bunch of nouns, you poor sod, add this behavior and this logic.
src/
├── Controllers/
│ ├── OrdersController
│ ├── PaymentsController
│ ├── ProductsController
├── Services/
│ ├── OrdersService
│ ├── PaymentsService
│ ├── ProductsService
├── Models/
│ ├── Order
│ ├── OrderItem
│ ├── Payment
│ ├── PaymentDetail
│ ├── Product
│ ├── ProductVariant
But…what are the existing beha-
Figure it out.
But…the order service uses the payments service which uses the prod-
Figure it out.
Where is the existing business logic for disc-
Everywhere.
Can…can i save orders, payments and product in one big transa-
You tell me.
Should all these tables have foreign keys to eac-
Up to you.
Where should i put this new refund fea-
FIGURE IT OUT!
This is the point at which the meatbag overlord takes to Twitter to complain that Codex is nerfed or Claude fell off. It’s time to try Grok or Gemini CLI. Or to take up “spec driven development” because the problem is anything other than their spaghetti-noun mountain. Sisyphus rolls up their sleeves, then rolls a longer prompt right back up the hill, only to watch the failure rolling back down again.
Some engineers will (correctly) point out that they don’t need DDD for simple CRUD apps. The durable business value of simple CRUD apps is debatable but the more important point is that very soon no one will need engineers for these simple CRUD apps. They will be the first to be supplanted by coding agents. When code is cheap, behavior & meaning rich systems increase in value.
DDD = Automatic Context
Coding agents NEED context. DDD provides it. Drop the same agent in a typical DDD codebase and…
src/
├── orders/
│ ├── controllers/
│ │ └── OrdersController
│ ├── domain/
│ │ ├── Order
│ │ ├── OrderItem
│ │ └── events/
│ │ ├── OrderPlaced
│ │ ├── OrderCancelled
│ │ └── OrderPaid
│ └── application/
│ ├── handlers/
│ │ ├── PlaceOrder
│ │ └── CancelOrder
│ └── integrationEventHandlers/
│ ├── WhenPaymentMade
│ └── WhenPaymentFailed
├── payments/
│ ├── controllers/
│ ├── domain/
│ └── application/
└── inventory/
├── controllers/
├── domain/
└── application/
Suddenly, the agent can see. The code is naturally modular (because bounded contexts). Transaction boundaries are explicit (aggregates). Possible actions and events are explicit. Side effects are explicit. Cross-module data flows are explicit. Code grouping is explicit. Potential team ownership and boundaries are explicit. The context is baked in. The structure itself encourages engineers to think about consistency, idempotency, concurrency & many other concerns.
Listen…That’s the sound of thousands of engineers wailing at the “horror” of “so many files” and “deep file hierarchies”. Think of all the friction & harm to DX (far more important to these folks than the long term health of the software or their customers’ experiences of it). This is too high a price to pay for the small reward of providing bountiful, immediate context to any person or machine with the misfortune of encountering their codebases. This is too heavy a burden to unlock outsized business outcomes.
Blank Canvas
Maybe it’s not worth it, all that “thinking” and blathering on about models. The simplicity gospel feels right in lockstep with where society is going anyway. Not simplicity as in elegant minimalism, but simplicity as in letting go of agency. Rules & meaning are fading in our everyday lives, why bother with that boomer talk in our code? Give in to the pull of the blank canvas.
Shhhhhhh…don’t think…just ship.