← All posts

Be careful when adopting Domain-Driven Design

By Yahya Eddhissa · · 8 min read

  • ddd
  • architecture

# The DDD misconception

Domain-Driven Design has a branding problem. Say “DDD” in most teams and someone will immediately think of hexagonal architecture, CQRS, event sourcing, or a very specific folder layout. None of that is wrong exactly, but none of it is DDD either. Those are independent patterns that get associated with the name so often that newcomers assume they’re required, and that confusion is where most of the damage starts.

If you want the real thing, start with Eric Evans’ blue book, not a blog post, not a conference talk, not an AI chatbot. It’s slow and dated in places, but it’s the only source that treats the strategic side of DDD (bounded contexts, ubiquitous language, the work of distilling a model from domain experts) as seriously as the tactical side (entities, aggregates, repositories). Evans already listed the pitfalls in the book itself, so I won’t repeat his list. What I’ll do here instead is share what went wrong when I tried to apply DDD on real projects, and what I’d tell myself before starting.

# Two sides, one name

DDD splits cleanly into two very distinct halves: a tactical half and a strategic half, and almost every mistake below comes from mixing the two up. The tactical half is the part people usually mean when they say DDD: entities, value objects, aggregates, repositories. The strategic half is the part that actually does the hard work: bounded contexts, ubiquitous language, and the process of pulling a model out of people who don’t think in code.

The “two sides of DDD” distinction is worth sitting with, because the tactics aren’t even unique to DDD. Repositories, value objects, and entities aren’t exclusive to DDD. You can use them effectively without adopting DDD, just as you can use DDD without adopting every architectural pattern commonly associated with it. What makes their use domain-driven is how they support a model derived from the business, rather than the mere presence of these abstractions in your codebase.

# Mistaking the tactics for the whole thing

Most of the pitfalls I ran into are really one pitfall wearing different outfits: treating the tactical patterns as if they were DDD in full, and skipping the modeling work they’re supposed to express.

The clearest case is the anemic model: entities that are just data bags with getters and setters, with all the actual business logic sitting in service classes around them. This usually happens when you build your “domain model” on top of an existing relational schema and just turn each table into an entity. You get objects that look like DDD, they’re named after domain concepts, they live in a domain package, but they behave like a thin wrapper over your database rows. No (or very few) invariants, no behavior, nothing you couldn’t do just as well with plain DTOs.

The folder layout is the same mistake one level up. Creating domain/, application/, and infrastructure/ directories and calling it a day gives you nothing if nothing inside them reflects a model built from conversations with domain experts. Renaming a Service class to DomainService and moving it into a domain folder doesn’t make a design domain-driven, it just makes it look like one on a slide.

It also shows up as a false sense of certainty about implementation. DDD doesn’t prescribe one correct way to build anything; it’s a philosophy for arriving at implementations, and the possibilities it leaves open are endless. I used to treat it as a fixed recipe: define entities, wrap them in aggregates, expose repositories, done. That’s backwards. The patterns are downstream of the model, and two teams solving the same problem with real domain knowledge can land on entirely different, equally valid designs.

And it’s why “event sourcing is not DDD” and “CQRS is not DDD” are worth saying out loud, even though they sound obvious once written down. Both are legitimate patterns on their own, and both get bolted onto “doing DDD” so often that people assume you need one to do the other. Most of the real damage happens when teams chase these tactical patterns and never do the strategic work: no bounded contexts, no real conversations with domain experts, just aggregates arranged around a schema that was already there.

Put all of that together and you get DDD treated as a silver bullet: something that will untangle a messy codebase just by being applied. It won’t. DDD gives you a vocabulary and a process for dealing with complexity that’s actually there in the domain. If your complexity is mostly accidental (unnecessary layers, over-abstracted interfaces, technology you didn’t need), DDD won’t fix that, and it can add its own overhead on top. I’ve seen teams reach for it assuming it would clean things up, only to end up with a mess that now also has aggregates.

# The boundary problem

If I had to single out the one mistake that actually breaks projects, it’s not enforcing boundaries. Evans has said that he considers bounded contexts and ubiquitous language the most important concepts in the book, more important than any of the tactical patterns. They’re also the easiest to skip, because they don’t produce code you can point to the same way an entity class does.

Without a bounded context, your “ubiquitous language” stops being ubiquitous. The same word means different things in different parts of the system, and nobody notices until a bug surfaces that no one can explain without a long meeting. A concrete smell to watch for is a domain model whose relationships mirror the relational schema without reflecting the business rules governing them. If you keep finding complex associations and many-to-many relationships between your entities, that’s usually a sign you haven’t actually found your aggregate boundaries yet, not that the domain is “just complex.”. It’s not always the case though, but it’s a strong indicator that you need to revisit your boundaries and ensure they align with the business rules rather than the database schema.

Many-to-many relationships and aggregate boundaries
Many-to-many relationships don't automatically imply incorrect aggregate boundaries. The real question is which invariants must be enforced together and which relationships can be managed independently. Which is the whole point of consistency boundaries.

# AI and the domain expert problem

DDD’s core work isn’t writing code, it’s extracting a model from domain experts who don’t think in code at all, then refining it over repeated conversations until it earns the name “ubiquitous language.” An AI assistant can write you an aggregate root, a value object, a repository interface, all the tactical scaffolding, in seconds. What it can’t do is sit across the table from your product owner and figure out whether “cancelled” and “voided” are the same concept in your business or two different ones that happen to look the same in the database. That distillation work is still entirely on you.

# When DDD is actually worth it

Given how often it goes wrong, here’s what I’d actually recommend. Don’t structure an entire project around DDD. Use it only where there are real consistency boundaries worth preserving, the parts of the system where the business logic is genuinely complex and getting it wrong is expensive. Start plain and adopt DDD incrementally as those boundaries reveal themselves, not upfront on day one.

Also don’t do it without domain experts. If you don’t have access to people who deeply understand the domain, you’re not modeling the business, you’re modeling your own assumptions about it, and you’ll introduce edge cases and wrong abstractions you don’t even know you’re introducing. At best, what you get without domain experts is “DDD-ish”: the vocabulary and the folder names, without the substance.

And finally, remember that not every piece of data needs to be consistent at all times, and not every invariant needs to be enforced in code the moment it’s written. Too much rigidity can kill a project as surely as too little. Some inconsistency is cheaper to tolerate and reconcile later than to prevent upfront with a pile of defensive code.

# Conclusion: DDD is not a silver bullet

DDD gave software design a vocabulary it was missing: ubiquitous language, bounded contexts, the distinction between core and supporting subdomains. That’s real value, and it’s worth keeping regardless of which tactical patterns you end up using, or whether you use any at all. But none of it replaces the work of actually sitting down with the people who understand the domain. Skip that part, keep only the folder names and the entity classes, and you haven’t adopted DDD. You’ve adopted its costume.

© 2026 Yahya Eddhissa

RSS