"Clean Architecture Is Overhead."
Stop counting lines of code
“Clean Architecture is overkill.”
I’ve heard that a lot of times.
Too many interfaces. Too much abstraction. Too much indirection.
And some people go further and argue it makes the system harder to maintain, harder to understand.
The overhead is real
Compared to a plain CRUD application, yes, there are more interfaces. Specifically for anything that is infrastructure: databases, the file system, external REST APIs.
Instead of directly opening an HTTP client connection, or directly opening a database connection, or directly working with the ORM, you put an interface in between. Your use cases depend on those interfaces, i.e. abstractions of infrastructure, rather than directly depending on the infrastructure details.
And as a developer you have more files to look at, because when you open your use case class you’re not going to see the direct call to the HTTP client.
That’s overhead. I’m not going to pretend it isn’t.
But it’s nothing compared to the size of the codebase
For midsize and larger projects, that overhead is nothing compared to the size of the codebase.
It’s like someone arguing: let’s not use Java/C#, let’s use C/C++ for everything, because C and C++ are faster. Microseconds, milliseconds, whatever the number is.
And yet developers still use Java and .NET anyway, because the trade-off is worth what they get back.
Same thing here.
A handful of extra interface files is not the argument they think it is.
The harder one: domain entities vs ORM entities
The complaint that’s genuinely harder to argue against is this one: you have your domain entities and you have your ORM entities, so now you’re doing double the work and maintaining double the code.
Initially, on a really small project, I can see that argument being hard to fight. At that point the domain entities and the ORM entities are most likely one-to-one identical. So yes, it does feel like duplication. It looks like you typed the same class twice for no reason.
But what happens, quite often, is that enterprise projects grow in complexity. And that’s exactly when being coupled to the database hurts you.
The one-to-one mapping stops being one-to-one, and the class they were calling duplication turns out to be the reason you can change the business logic without being trapped by your database structure.
Stop counting lines of code
Instead of asking: “How many extra files does Clean Architecture create?” or “How many extra lines of code does this add?”…
Think about how hard it is to understand the code.
Separation of concerns means separating business logic from infrastructure so that it’s easier for our brains.
When you’re thinking about business requirements, you don’t want to also be thinking about what the external REST API DTO looks like. Later when you shift to integrating with external systems, that’s when you think about the nitty-gritty of the I/O and the DTOs. Those are two separate concerns. You go into one of them at a time.
That’s less effort to understand the code. That’s the payoff, and it doesn’t show up anywhere in a line count.
So next time someone tells you Clean Architecture is overhead, don’t argue about the number of files. Ask them how many things they have to hold in their head to change one business rule.
⚡Clean Architecture in practice
How do you separate business logic from infrastructure?
How to decouple the domain from the ORM?
When is an abstraction useful—and when is it over-engineering?
Join the live training session:
🗓 Clean Architecture for Backend Developers (Wed Aug 26)


Great article, especially the reframing at the end:
Not “how many files,” but “how much do I need to keep in mind to change a rule?” That’s the real crux of the matter, and I think it can be further clarified in two areas.
**Testing:** The same distinction pays off in testing as well—not simply as “more” or “faster” tests, but as a different kind of test. If the business logic isn’t tied to an ORM, database connection, or HTTP client, it can be validated with simple, cost-effective unit tests—without test containers, without in-memory databases, and without network mocks. When testing a business rule, you don’t have to figure out how to simulate the infrastructure for it simultaneously—it’s the same separation of concerns, just applied to testing instead of coding.
It’s important to note the limitation: This does not replace integration or contract tests at the adapter boundaries. If you rely solely on mocks, you’ll eventually end up testing your own assumptions about the infrastructure rather than the infrastructure itself. The actual effect is better distribution: many inexpensive unit tests for the domain, a few expensive integration tests at the edges—a clean test pyramid that’s hardly achievable without this separation.
**The entity point you mention:** In my experience, this is the most underestimated in the entire article. At first, the domain entity and the ORM entity really do look like pure duplicates—one class, typed twice. But the risk doesn’t become apparent while writing the code; it only emerges later, with the first change.
Without this abstraction, the database structure is effectively passed all the way through to the UI. And with that, the change footprint of every subsequent modification grows: a column change in the database ripples through the ORM, business logic, and API response all the way to the front end—because the same entity is used everywhere, instead of being translated at the domain boundary. This is essentially change coupling between layers that should actually evolve independently of one another.
I’m currently focusing a lot on exactly this kind of structural coupling—how to make it visible in real codebases before it becomes a problem, not after. And one pattern that keeps cropping up: Teams don’t realize just how costly the lack of separation actually was until the third or fourth “trivial” change. At that point, the initial “duplication” suddenly looks very inexpensive by comparison.