All postsSoftware Architecture

Clean Architecture or Not? Why the Real Question Is Autonomy and Pragmatism

Jul 20, 2026·14 min read

Few ideas in modern software design have been as widely adopted — and as widely argued over — as Clean Architecture. Popularized by Robert C. Martin ("Uncle Bob") in a 2012 blog post and later expanded into a full book, it gave a name and a diagram to something many senior engineers were already doing instinctively: keeping business logic independent of frameworks, databases, and UI technology, so that the parts of a system most likely to change don't drag the parts that matter most down with them.

More than a decade later, the community is split, but the split usually hides a more useful question underneath it. "Should I use Clean Architecture?" isn't really a yes-or-no question about a specific pattern — it's a test of whether an engineering team has the autonomy to make that call themselves and the pragmatism to base it on the project's actual goals, rather than inheriting the decision from a template, a tutorial, or an org-wide mandate. Teams that lose either half of that — the freedom to decide, or the discipline to decide honestly — tend to land in one of two ditches: rigid over-engineering on one side, or drift into an unstructured mess on the other. Skip the decision entirely, and the outcome usually isn't "no architecture" at all — it's almost always the same well-documented result: a system that gradually turns into a Big Ball of Mud.

The Real Alternative to Deciding Is Mud, Not Freedom

It's worth naming the risk on the other side of this debate plainly, because it rarely gets the attention the over-engineering critique does. In their well-known 1997 paper, software researchers Brian Foote and Joseph Yoder gave a name to what they called the most common architecture actually deployed in practice: the Big Ball of Mud — a system whose structure, if it has any at all, was shaped by expediency rather than design. It isn't a lack of pattern so much as the default pattern that emerges whenever nobody makes an explicit architectural decision and sticks to it. Information leaks promiscuously between distant parts of the system, boundaries blur, and eventually nobody can say with confidence where a given piece of logic is actually supposed to live.

This is the context that makes "just don't do Clean Architecture" bad generic advice. Engineers who avoid Clean Architecture because it feels heavy don't automatically land somewhere lighter and cleaner instead — far more often, in the absence of any deliberate structural decision, they land in mud, because mud is what happens by default under shipping pressure, not something a team chooses on purpose. The failure mode most teams actually hit isn't "too much architecture." It's "no decision about architecture at all," repeated across a hundred small choices until the system can't be reasoned about anymore. That drift is what a lack of pragmatism looks like in practice — not choosing the wrong pattern, but never actually choosing.

That's the case for giving engineering teams real autonomy to make a bold, explicit call early — Clean Architecture, something lighter, or something else entirely — and then holding them to using that autonomy pragmatically, rather than letting the structure of a codebase get decided implicitly, one shortcut at a time.

What the Idea Actually Solves

At its core, Clean Architecture is built around one rule: dependencies should point inward, toward the business logic, never outward toward infrastructure. Your domain model shouldn't know or care whether it's backed by SQL Server, PostgreSQL, or a flat file. Your use cases shouldn't know or care whether they're triggered by a REST controller, a message queue, or a console command.

This buys real things:

  • Separation of concerns. Business rules stop leaking into controllers, ORMs, and UI code, and vice versa.
  • Testability. Because the domain doesn't depend on a database or an HTTP server, you can unit test business behavior with plain objects and fakes, not integration harnesses.
  • Maintainability over time. Systems tend to live far longer than their original authors expect. Clear boundaries make it easier for developers who join years later to figure out where a given piece of logic actually belongs.
  • Framework independence. Frameworks and libraries become swappable implementation details sitting behind interfaces, rather than the load-bearing foundation the whole codebase is built on top of.

None of this is controversial in the abstract. Almost every architectural style that predates or parallels Clean Architecture — Alistair Cockburn's Hexagonal (Ports & Adapters) Architecture, Onion Architecture, and others — is reaching for the same underlying goal by a different route: isolate the stuff that's expensive to change from the stuff that changes constantly.

Where the Criticism Actually Comes From

It's worth being precise here, because most of the pushback isn't really aimed at the principles themselves. Almost nobody seriously argues that business logic should be tightly coupled to a specific ORM. The criticism is aimed at how the principles get applied in practice — and three patterns come up again and again.

Architecture before problems exist. Plenty of projects start with a Repository, a Generic Repository, a Unit of Work, CQRS, a mediator library, an auto-mapper, validators, DTOs, and interfaces for all of it — before a single business requirement has actually been implemented. Days get spent scaffolding structure instead of delivering value, on the assumption that the structure will pay for itself later. Sometimes it does. Often the project never grows into the complexity the scaffolding was built for.

Over-abstraction for its own sake. A single simple feature can end up spread across eight or nine files — a command, a handler, a repository interface, a repository implementation, a validator, a DTO, a mapping profile, a response object — for logic that would have fit comfortably inside one method. The code becomes mechanically correct and cognitively expensive at the same time: technically clean, practically hard to hold in your head.

Interfaces that exist to satisfy a rule, not a need. Creating IUserService next to UserService when there is exactly one implementation and no realistic prospect of a second isn't abstraction — it's ceremony. Interfaces are supposed to abstract behavior that genuinely varies. Wrapping Entity Framework, which is already an abstraction over raw data access, in another generic repository layer that does nothing but forward the same call downward adds a layer without adding value.

This is really where the "creativity killer" complaint comes from, and it's more precisely a complaint about lost autonomy than lost creativity. The concern isn't that patterns exist — it's that they become mandatory. Instead of asking what's the simplest solution to this specific problem, teams start asking what does the architecture template say I should do here, and the framework starts making decisions that should belong to the engineer looking at the actual problem in front of them. An engineer who can't deviate from a prescribed structure when the situation calls for it isn't practicing architecture — they're following a checklist someone else wrote for a different project.

Cargo Cult Architecture

There's a well-known term for imitating a successful system's outward form without understanding why it worked: cargo cult programming. It's not hard to see the pattern repeat inside Clean Architecture codebases — CQRS added because a well-known template uses it, a mediator library wired in because every tutorial does, repositories generated because the project scaffold includes them by default, none of it tied back to an actual requirement the project has. The architecture stops being a tool for reasoning about a specific system and starts being a ritual performed because it's what "good" projects are supposed to look like.

Martin Fowler's long-standing writing on YAGNI — "You Aren't Gonna Need It" — is a useful counterweight here, even though it predates Clean Architecture's popularity by years. The core idea is the same one that shows up in the more thoughtful critiques of Clean Architecture: build the abstraction when a real, current requirement demands it, not because a future requirement might.

A Real Alternative, Not Just a Complaint: Vertical Slice Architecture (VSA)

The most useful pushback on Clean Architecture doesn't stop at criticism — it offers a concrete alternative. In 2018, developer Jimmy Bogard published "Vertical Slice Architecture", arguing that traditional layered approaches force nearly every request through the same rigid path — controller talks to service, service talks to repository — even when a given feature doesn't actually need that much ceremony. His alternative organizes code around individual features or use cases instead of technical layers: each "slice" contains everything needed to handle one request, end to end, with the explicit goal to minimize coupling between slices while accepting more coupling within one.

VSA isn't a rejection of structure — it's a different bet about where the structure should live. Clean Architecture optimizes for consistency across the whole system: every feature goes through the same layers, which makes the codebase predictable but can force simple features through unnecessary machinery. Vertical Slice Architecture optimizes for isolation between features: a simple CRUD endpoint can stay simple, while a genuinely complex use case can carry as much internal structure as it actually needs, without that complexity leaking into features that don't share it. The trade-off shows up on the other side too — critics of VSA point out that without shared layers, related slices can end up duplicating logic that a layered architecture would have naturally centralized.

Neither approach is objectively better. They're different answers to the same underlying design question — where do you want your coupling to live, and what are you optimizing for — and the right choice depends entirely on the shape of the actual problem, not on which pattern is trending in blog posts this year.

Autonomy Means Someone Has to Decide — Pragmatism Means Deciding Honestly

This is ultimately a trade-off question, not a right-or-wrong one — and it's exactly the kind of question that belongs with the engineering team closest to the problem, not a company-wide architecture mandate handed down in advance of ever seeing the project. Autonomy without pragmatism just produces a different flavor of dogma — a team free to choose whatever they personally prefer, disconnected from what the project actually needs. Pragmatism without autonomy is worse: a team correctly diagnoses that a project doesn't need nine layers, but ships nine anyway because the org-wide template says so. The two only work together. Before writing a line of code, it's worth stating plainly what the project is actually optimizing for: shipping speed this quarter, or maintainability over five years with a rotating cast of engineers? A single team that owns the whole stack, or a large org where infrastructure providers might genuinely change? A domain with real business complexity, or a thin CRUD layer over a database? The answer to those questions, decided on purpose by the people building the thing and written down, is the actual architecture decision — Clean Architecture, Vertical Slice Architecture, or a straight line from controller to data context are just the possible outputs of it.

Clean Architecture tends to earn its keep when the goals point toward:

  • large, long-lived teams where many people touch the same codebase over years
  • genuinely complex business domains (banking, healthcare, insurance, logistics, ERP)
  • multiple infrastructure providers or the realistic likelihood of swapping one out
  • requirements that are still actively evolving

In those environments, the cost of the extra layers is paid back many times over by the ease of onboarding new developers, isolating changes, and testing business rules without spinning up a database.

Vertical Slice Architecture tends to earn its keep when the goals point toward:

  • feature teams that need to move fast and independently, without waiting on shared layers
  • systems made up of many loosely related use cases rather than one deeply intertwined domain
  • a preference for keeping each feature's complexity contained to that feature, even at the cost of some duplication

And a straight, undecorated controller-to-database path earns its keep when the goals point toward:

  • small CRUD APIs and internal tools
  • early-stage prototypes and MVPs
  • proof-of-concepts meant to validate an idea, not scale to production

For projects like these, skipping the extra layers isn't a shortcut taken out of laziness — it's the right amount of architecture for the actual size of the problem, as long as it was chosen on purpose rather than defaulted into. As Uncle Bob himself wrote in the original post, the point of architecture is to minimize the human effort required to build and maintain a system — not to maximize the number of layers it contains. Adding structure a project doesn't need works against that same goal. So does adding none at all and hoping the mud stays shallow.

A Practical Way to Decide

Rather than asking "should this project use Clean Architecture?" as a yes-or-no question, it's more useful to ask what problems the project actually has. If the honest answer includes complex business rules, a real need for infrastructure independence, or requirements that are clearly going to keep evolving, architectural boundaries are worth their cost. If the honest answer is "it's a CRUD API with three endpoints," simplicity usually wins, and the boundaries can always be introduced later, once the complexity that justifies them actually shows up.

A decent working rule: introduce an abstraction when it solves a problem you actually have, not because a template or tutorial includes it by default. It's the same discipline Fred Brooks was gesturing at decades earlier in his essay "No Silver Bullet" — there is no single technique, pattern, or architecture that eliminates the inherent difficulty of building software. Clean Architecture reduces some kinds of pain (coupling, testability, long-term maintenance cost) in exchange for other kinds (more files, more indirection, more upfront decisions). Pretending it's free in either direction — free to adopt everywhere, or free to skip everywhere — is where teams get into trouble.

The Takeaway

Clean Architecture isn't inherently good or bad; it's a set of trade-offs with a name attached, and it's one of several reasonable answers to a question every project has to answer somehow. Separation of concerns, dependency inversion, and long-term maintainability are real, valuable properties, and the diagram Uncle Bob published in 2012 gave a lot of teams a shared vocabulary for reasoning about them. The trouble starts when those principles calcify into rules applied regardless of context — when a five-endpoint internal tool gets the same nine-layer treatment as a core banking platform, not because it needs it, but because that's simply what "proper" architecture is assumed to look like. That's a failure of autonomy: the team closest to the problem didn't get to make the call.

But the opposite failure is at least as common, and gets talked about far less: teams that never make the call at all, and drift by default into the Big Ball of Mud that Foote and Yoder described decades ago — not because anyone chose it, but because nobody chose anything else in time. That's a failure of pragmatism: even with the freedom to decide, nobody did the honest work of matching the structure to the goal.

The best engineering teams hold both at once. They're given — or take — the autonomy to choose Clean Architecture, Vertical Slice Architecture, or plain simplicity on a case-by-case basis, and they use that freedom pragmatically: stating the goal, picking the structure that actually serves it, and revisiting the choice when the goal changes, rather than defending whatever was chosen first out of habit or convenience. Architecture is supposed to serve the software being built — and the worst version of it isn't the wrong pattern. It's a decision nobody was free to make, or one nobody bothered to make honestly.


References and Further Reading