Architecture Realities: The Myth of "Clean Architecture" in Production
Introduction:
Clean Architecture has a compelling promise. Separate your business logic from your infrastructure concerns, define clear boundaries between layers, depend on abstractions rather than implementations, and your system will be maintainable, testable, and adaptable to change. The diagrams are elegant. The principles are sound. The books are persuasive.
Production has a different opinion.
Systems that are built with strict adherence to clean architecture principles frequently encounter a gap between the theoretical elegance of the design and the practical reality of operating software under real constraints. The gap is not because the principles are wrong. It is because production systems exist in conditions that clean architecture diagrams do not account for — performance requirements, operational complexity, team constraints, and the accumulated weight of decisions made under time pressure that no architectural principle can fully anticipate.
Understanding where clean architecture helps and where it hurts is more useful than either uncritical adoption or cynical rejection.
The Abstractions That Protect You Also Slow You Down:
Clean architecture's core mechanism is abstraction — separating concerns behind interfaces so that each layer can change independently. This works as designed when change actually occurs in the way the architecture anticipated. It costs more than it saves when the abstractions add indirection without enabling the flexibility they were designed to provide.
A repository interface that abstracts database access behind a generic interface protects business logic from database implementation details. It also hides the specific query behaviour, transaction semantics, and performance characteristics of the database being used. When a performance problem requires understanding exactly which queries are being generated and how they interact with database indexes, the abstraction that was designed to simplify things makes the investigation significantly harder.
The cost of abstraction is not always visible during development. It becomes visible during debugging, during performance optimisation, and during incidents — precisely the situations where the system needs to be understood quickly.
Layer Boundaries Become Bureaucratic:
In clean architecture, each layer has a defined responsibility and a defined direction of dependency. Business logic does not depend on infrastructure. Infrastructure depends on business logic through interfaces. Data flows through defined boundaries with explicit mapping between representations at each layer.
In practice, these boundaries generate significant boilerplate. Data that originates in a database must be mapped to a domain object, then mapped to a use case input, then mapped to a response model before it reaches the caller. Each mapping is a translation layer that must be written, tested, and maintained. For simple data flows, the mapping code is longer and more complex than the business logic it surrounds.
Teams building under time pressure — which is most teams, most of the time — find that strict layer boundaries slow them down without proportional benefit for the features they are building. The response is to bend the boundaries — passing database entities directly to response serialisers, putting business logic in controllers, calling repositories from places the architecture did not intend. The architecture becomes a guide that is honoured inconsistently rather than a constraint that is enforced reliably.
Testability Gains Are Real but Overstated:
One of the strongest arguments for clean architecture is testability. By depending on abstractions rather than implementations, business logic can be tested in isolation using mock implementations of its dependencies. Tests run fast, require no infrastructure, and cover edge cases that would be difficult to reproduce in integration tests.
This benefit is real. Pure business logic that has no direct dependencies on databases, message queues, or external services is genuinely easier to test than logic that is entangled with infrastructure concerns.
But the benefit is frequently overstated. Mock-heavy unit test suites test that code behaves correctly given specific mock responses. They do not test that the system behaves correctly when those mock responses are replaced by real infrastructure that has its own failure modes, its own performance characteristics, and its own edge cases. Teams with comprehensive unit test suites built on mocks still encounter production failures that no unit test anticipated — because the tests were testing the logic in isolation from the infrastructure that the logic depends on in production.
The Domain Model Rarely Stays Pure:
Clean architecture places the domain model at the centre of the system — a pure representation of business concepts that has no dependencies on infrastructure concerns. This domain model is supposed to remain stable as infrastructure changes, providing a stable core that outlasts any particular database, framework, or external service.
In practice, domain models accumulate infrastructure concerns over time. Performance requirements lead to denormalisation that reflects database access patterns rather than business concepts. Serialisation requirements lead to annotations that reflect wire format concerns. Framework integration leads to base classes and lifecycle methods that have nothing to do with the business domain.
The pure domain model is an aspiration that production systems consistently erode. Each erosion is individually justified — this annotation makes serialisation significantly simpler, this base class reduces boilerplate across the team. The cumulative effect is a domain model that is cleaner than it would be without the architectural intent but dirtier than the architecture diagrams suggest it should be.
Most Systems Do Not Need That Much Flexibility:
Clean architecture optimises for flexibility — the ability to swap out infrastructure components without affecting business logic. Change your database, change your framework, change your external service provider, and the business logic should be unaffected because it depends on abstractions rather than implementations.
This flexibility is genuinely valuable for systems that experience this kind of change frequently. For most production systems, it is optimising for a scenario that rarely occurs. The database that was chosen at the start of the project is rarely replaced. The framework that was selected is upgraded in place rather than swapped for a different one. The external service provider is changed infrequently and the change is a significant enough project that the architectural flexibility clean architecture provides is a minor factor in the overall effort.
Building and maintaining the abstractions that enable this flexibility has a real ongoing cost. For systems that never exercise the flexibility, that cost is overhead with no return.
Pragmatic Architecture Outperforms Pure Architecture:
The most maintainable production systems are not the ones that most strictly adhere to clean architecture principles. They are the ones that apply those principles where they provide genuine value and relax them where strict adherence generates overhead without benefit.
Separating business logic from infrastructure concerns is valuable. Doing it through multiple layers of mapping and abstraction for every data flow in the system is not. Testing business logic in isolation is valuable. Building mock-heavy test suites that never exercise real infrastructure is not. Defining clear ownership boundaries between components is valuable. Enforcing those boundaries through rigid layer constraints that slow down simple operations is not.
Pragmatic architecture applies principles in proportion to the problems they solve. It accepts that production systems will be messier than architectural diagrams and designs for that messiness rather than pretending it will not occur.
Conclusion:
Clean architecture is not a myth because its principles are wrong. It is a myth because the version that exists in architecture books and conference talks is cleaner, more consistent, and more flexible than the version that survives contact with production systems, time pressure, and the accumulated decisions of real engineering teams.
The principles of clean architecture — separation of concerns, dependency inversion, testability — are worth understanding and worth applying. The strict implementation of those principles as a complete architectural system is worth questioning, especially for teams that are paying the cost of the abstraction without exercising the flexibility it provides. Production systems deserve architecture that works under real constraints, not architecture that works in diagrams.
Enjoyed this post?
Stay in the loop
New posts + weekly digest, straight to your inbox.
Create a free account
- Save posts to your vault
- Like posts & build history
- New-post alerts
No comments yet. Be the first to comment!