Clean Architecture is an approach to software design built to keep systems maintainable, but software rarely becomes difficult to maintain simply because developers suddenly forget how to write code. Instead, the trouble almost always starts with hundreds of small, seemingly harmless decisions.
For instance, a database call gets placed directly inside a controller because it is quicker. Meanwhile, business rules become tightly tied to a specific framework. Eventually, an API response model starts being used throughout the entire application, and a third-party SDK appears in places where it does not really belong.
At first, none of these choices seems particularly dangerous on its own. However, as time goes on, the application grows.
Consequently, six months later, changing a single database column touches 30 separate files. Furthermore, testing a simple pricing rule requires starting half of the application. As a result, upgrading a framework becomes a massive, risky project rather than a routine dependency update.
Fortunately, this is precisely the kind of problem Clean Architecture is designed to prevent.
As a senior polyglot software engineer, I have worked across different languages, frameworks, databases, and deployment models over the years. Through this experience, one key lesson carries across nearly all of them: technologies change much faster than the business problems software is supposed to solve.
Therefore, Clean Architecture gives us a clear way to acknowledge that reality.
Specifically, instead of designing an application around React, Spring Boot, .NET, Django, PostgreSQL, MongoDB, or AWS, we design it around what the application actually does. In this model:
-
First, the framework becomes a detail.
-
Second, the database becomes a detail.
-
Ultimately, the business logic remains at the very center.
What Is Clean Architecture?
In simple terms, clean architecture is an approach to software design that strictly separates core business rules from implementation details such as databases, frameworks, user interfaces, external APIs, and infrastructure.
The idea is strongly associated with software engineer and author Robert C. Martin. However, it also shares core concepts with earlier approaches, including Hexagonal Architecture, Ports and Adapters, and Onion Architecture.
For example, Microsoft describes Clean Architecture as an approach where business logic and the application model sit firmly at the center of the application. Thus, instead of business logic depending directly on infrastructure, infrastructure depends on abstractions defined closer to the application core.
Similarly, AWS describes a parallel idea in its guidance for hexagonal architecture: isolate business logic from infrastructure such as databases and external APIs so components remain loosely coupled and significantly easier to test.
Although that sounds highly architectural, the practical idea is actually very simple: always protect the important code from the code most likely to change.
For instance, if your company sells insurance, your core insurance rules matter far more than the specific ORM you currently use. Likewise, if you operate an e-commerce platform, your pricing and order rules matter much more than whether the application stores information in PostgreSQL or DynamoDB. Ultimately, clean architecture tries to reflect that fundamental difference in the structure of the software.
Why Clean Architecture Matters
To illustrate this, imagine an online ordering application. When a customer places an order, the application must perform several tasks:
-
Verify the items
-
Calculate prices
-
Apply discounts
-
Calculate tax
-
Verify inventory
-
Process payment
-
Create the order
-
Send confirmation
-
Update fulfillment
Now, imagine all of this logic lives inside a single HTTP controller. As a result, the controller knows about the database, it knows about Stripe, it knows about email, it knows about JSON, and it knows about the web framework. In fact, somewhere in the middle of all that technical plumbing, it also contains the actual business rules.
Technically, the application works. However, architecturally, it is extremely fragile.
For example, testing a simple discount calculation may require mocking the database, the payment service, the framework request, and the email system simultaneously. Furthermore, changing one technical component can unexpectedly break unrelated business behavior.
In contrast, clean architecture separates those concerns entirely. Indeed, the core business rule for calculating a discount should not care whether the request arrived through REST, GraphQL, a command-line tool, or an asynchronous message queue. Ultimately, that independence is where much of the architectural value comes from.
The Dependency Rule: The Heart of Clean Architecture
If you remember only one thing about clean architecture, remember the dependency rule: dependencies must always point inward toward the business core.
In other words, the inner parts of the application should never know about the outer technical details.
A simplified view looks like this:
Frameworks & Infrastructure → Adapters → Application → Domain
(Not: Domain → Database → Framework)
Indeed, this distinction fundamentally changes how software evolves.
Suppose your application contains this central business operation: CalculateOrderTotal. That operation naturally needs product information. In a tightly coupled implementation, it might directly create a PostgreSQL repository and execute SQL queries. Consequently, the core business operation now depends directly on PostgreSQL.
With clean architecture, however, the application depends on an abstraction instead: ProductRepository. Then, the infrastructure layer provides the actual concrete implementation: PostgresProductRepository.
Thus, the business code knows what capability it needs, but it does not know or care how that capability is implemented under the hood. In practice, this is dependency inversion at work.
The Main Layers of Clean Architecture
Different teams often use different names for these layers, and that is completely fine. After all, the specific names matter much less than the direction of the dependencies. In general, a practical clean architecture contains four broad areas:
1. Domain
The domain layer contains the most important business concepts and rules. Specifically, you will find:
-
Entities
-
Value objects
-
Domain rules
-
Domain services
-
Business validation
-
Business exceptions
For instance, in an e-commerce system, examples might include Order, OrderItem, Money, Product, Discount, and ShippingPolicy. Crucially, these concepts should have little or no knowledge of databases, HTTP frameworks, cloud providers, or UI technologies.
2. Application
Next, the application layer coordinates what the software actually does by orchestrating user intent. This is where you commonly find use cases such as:
-
PlaceOrder -
CancelOrder -
RegisterCustomer -
ApproveLoan -
GenerateInvoice -
ResetPassword
Although a use case works directly with domain objects and interfaces, it should not need to know whether data persistence happens through MySQL, PostgreSQL, MongoDB, or an external API.
3. Interface Adapters
In addition, interface adapters translate data between the application’s preferred representation and the outside world. Common examples include:
-
Controllers
-
Presenters
-
API handlers
-
Repository implementations
-
Serializers
-
Mappers
Think of adapters simply as translators. For example, while the web framework speaks HTTP and JSON and the database speaks SQL, your core business logic should not need to speak either language directly.
4. Frameworks and Infrastructure
Finally, the outermost layer contains highly replaceable technology components, including:
-
Databases
-
Web frameworks
-
Message brokers
-
Cloud services
-
Email providers
-
File systems
-
Payment gateways
-
Third-party SDKs
While these tools are necessary, they should never define your underlying business model.
10 Clean Architecture Principles I Use in Real Projects
1. Put Business Rules at the Center
Always start with the actual problem your software solves. For instance, if you are building payroll software, model payroll concepts first. Similarly, if you are building logistics software, model shipments, routes, packages, and delivery rules.
Therefore, do not begin by asking: “What database tables should we create?” Instead, ask: “What does the business need the system to do?” After all, database tables are merely implementation choices, whereas business behavior is the primary reason the application exists.
2. Keep Frameworks Outside the Core
Frameworks are undeniably useful because they provide routing, dependency injection, security, serialization, and logging. However, frameworks also change and evolve over time.
In fact, I have seen teams build applications so deeply tied to a framework that upgrading it became almost as difficult as rewriting the entire application. Therefore, your business logic should not require a framework simply to exist. Ideally, you should be able to instantiate and test important domain objects using plain programming language features.
3. Treat the Database as an Implementation Detail
This principle often sounds surprising at first because databases are obviously critical. Nevertheless, the database should not define the structure of the application.
Suppose the business needs to find a customer: CustomerRepository.findById(customerId). In this scenario, the application cares about finding the customer, but it does not care whether the underlying database is PostgreSQL, SQL Server, DynamoDB, MongoDB, or Redis.
To be clear, this separation does not mean changing databases suddenly becomes effortless, since data migration remains complex. Rather, it means your core business rules do not need to be modified simply because your persistence layer changes.
4. Design Around Use Cases
One of the best improvements teams can make is organizing application code around actual business actions. Instead of thinking exclusively in generic technical categories like Controllers, Services, and Repositories, think about explicit behaviors:
-
CreateOrder -
ShipOrder -
RefundPayment -
RegisterUser -
SuspendAccount -
GenerateStatement
As a result, the codebase becomes much easier to navigate because its folder structure directly describes what the system actually does.
5. Depend on Interfaces at Important Boundaries
Interfaces are particularly valuable when they protect the core application from volatile external systems. For example, a generic PaymentGateway interface might be implemented by a specific StripePaymentGateway. Consequently, your checkout use case depends on the capability to process payments rather than on Stripe’s specific SDK. Thus, if you change payment providers later, most of your checkout logic remains untouched.
However, do not create interfaces blindly for every single class. Instead, abstractions should protect genuinely meaningful boundaries; otherwise, they just add unnecessary noise.
6. Keep Data Crossing Boundaries Simple
A common mistake is allowing database entities or framework-specific request objects to leak throughout the entire application. Unfortunately, this creates hidden coupling.
For example, your domain layer should not depend on an ORM model filled with database annotations. Instead, translate data at architectural boundaries whenever that separation provides real clarity. Granted, writing mapping code requires extra effort up front, but those extra lines of code often save hours of painful debugging later.
7. Make Business Logic Easy to Test
This is one of the quickest ways to evaluate an architecture. Specifically, take an important business rule and ask: “Can I test this without starting a database, web server, or cloud environment?”
If the answer is no, you are likely dealing with unnecessary technical coupling. In contrast, clean architecture enables rapid unit testing because core business logic remains fully independent from infrastructure. Furthermore, fast tests encourage developers to run them frequently during daily development.
8. Isolate External Services
Modern applications rely heavily on third-party services for payments, email, SMS, search, identity, and analytics. However, every external system represents something your team does not fully control.
Therefore, put strict boundaries around them. Specifically, instead of spreading vendor SDK calls across 50 different files, place the integration behind a single application-owned abstraction. As a result, you gain one centralized place to handle authentication, retries, failures, and future vendor migrations.
9. Do Not Confuse Clean Architecture With Excessive Code
This is where some engineering teams stumble. They discover clean architecture and suddenly create an interface, implementation, factory, DTO, mapper, command, handler, repository, adapter, and gateway for every single feature. Consequently, a task that could have taken 30 lines of code ends up requiring 12 separate files.
However, that is not automatically good architecture. Ultimately, architecture should reduce the cost of future changes, not satisfy an abstract diagram. In fact, AWS explicitly recommends starting simple when adopting these patterns, especially for early MVPs. Therefore, apply boundaries where complexity justifies them, but avoid over-engineering simple features.
10. Let the Architecture Grow With the System
A small five-page internal tool does not require the same architectural complexity as a global banking platform. Hence, clean architecture should always be applied according to actual risk and scale.
Early in a project, you might start with just a few basic layers: Domain, Application, Infrastructure, and API. Then, as the application grows in complexity, those boundaries can become more granular. In short, the ultimate goal is not maximum separation—it is useful separation.
A Simple Clean Architecture Example
To see how these concepts fit together, consider a customer registration feature.
-
First, an HTTP request arrives:
POST /customers. -
Next, the controller parses the request body and constructs an application command.
-
Then, the application executes the
RegisterCustomeruse case. -
Subsequently, the use case validates the business rules, creates a
Customerdomain object, and callsCustomerRepository.save(customer). -
Notice that the application layer only interacts with the
CustomerRepositoryinterface, whereas the infrastructure layer provides the concretePostgresCustomerRepository. -
Finally, after saving, the application triggers
NotificationService.sendWelcomeMessage(customer), which infrastructure fulfills via an email provider.
Throughout this entire process, the overall dependency flow remains consistent:
HTTP → Controller → RegisterCustomer → Domain
Consequently, the core registration rules do not care about HTTP, PostgreSQL, or which email vendor is being used. That is clean architecture delivering practical value.
Clean Architecture vs. Layered Architecture
Traditional layered architecture typically follows a top-down structure:
Presentation → Business Logic → Data Access → Database
While this traditional structure is simple, problems arise when the business layer depends directly on data-access implementations.
In contrast, clean architecture reverses these critical dependencies. Specifically, instead of business logic depending on infrastructure, infrastructure depends on interfaces owned by the application core. As a result, the business core remains stable even when surrounding technologies change.
Clean Architecture vs. Hexagonal Architecture
Clean architecture and Hexagonal Architecture (Ports and Adapters) are closely related. In fact, Hexagonal Architecture focuses primarily on isolating application logic from external systems through explicit ports (interfaces) and adapters.
Similarly, clean architecture builds on these exact principles while offering a more defined layered structure. Ultimately, debating whether a design is strictly “Clean,” “Hexagonal,” or “Onion” is far less productive than asking: “Are our core business rules protected from unstable implementation details?”
When Should You Use Clean Architecture?
Clean architecture is particularly valuable for projects that feature:
-
Complex business rules
-
Long expected software lifetimes
-
Multiple third-party integrations
-
High requirements for automated testing
-
Evolving technology stacks
-
Multiple development teams working together
For instance, enterprise software, financial platforms, healthcare systems, SaaS products, e-commerce engines, and logistics platforms all benefit significantly from these boundaries.
When Clean Architecture May Be Overkill
On the other hand, not every application needs multiple architectural layers. For example, a basic CRUD application, a temporary internal script, or a short-lived proof of concept rarely justifies extensive separation.
After all, every boundary adds concepts that developers must understand and maintain. Therefore, instead of asking whether every project should use clean architecture, ask: “Which specific parts of this application actually need protection from change?”
The Biggest Mistake: Architecture by Folder
I have reviewed many codebases that looked perfectly structured in the directory tree:
domain/
application/
infrastructure/
adapters/
controllers/
repositories/
However, upon opening the domain files, I found direct imports from ORMs, frameworks, and cloud SDKs. In other words, while the folders appeared clean, the actual code dependencies were completely tangled.
Therefore, remember that clean architecture is not a folder layout—it is a dependency strategy. In fact, you can have neat folders with terrible architecture, just as you can maintain excellent architectural boundaries inside a simple directory structure. Always inspect your dependency directions rather than judging architecture by folder names alone.
Final Thoughts
After working across various languages and technology stacks, I have become far less interested in rigid architecture diagrams and much more interested in one practical question: How expensive will the next change be?
That is where clean architecture earns its keep. By separating concerns, a solid architecture allows core business logic to survive framework upgrades, database changes, and API migrations. Moreover, it makes critical application behavior straightforward to test and gives teams clear places to write new features.
In conclusion, use these principles as practical tools rather than rigid rules. Keep your business domain at the center, push volatile technologies to the outer edges, and focus on making future changes as painless as possible.
Frequently Asked Questions About Clean Architecture
What is clean architecture in simple terms?
Clean architecture is a way of organizing software code so that business rules are cleanly separated from technologies like databases, frameworks, UIs, and external APIs. As a result, the software becomes easier to test, maintain, and modify over time.
Who created Clean Architecture?
The term is primarily associated with Robert C. Martin (Uncle Bob). However, his model synthesizes concepts from earlier architectural patterns, including Hexagonal Architecture, Ports and Adapters, and Onion Architecture.
What is the main rule of clean architecture?
The core rule is the Dependency Rule: source code dependencies must always point inward toward core business logic. Therefore, inner layers should never depend directly on outer infrastructure details.
What are the main layers of clean architecture?
Most implementations feature four core layers: Domain Entities, Application Use Cases, Interface Adapters, and Frameworks/Infrastructure. Note that exact names may vary depending on the team or language.
Is clean architecture the same as microservices?
No, they operate at different levels. Clean architecture dictates how code and dependencies are structured within an application, whereas microservices describe how an entire system is split into independently deployable services. In fact, an individual microservice often uses clean architecture internally.
Is clean architecture only for object-oriented programming?
No. Although it is frequently discussed in OOP contexts, the core concepts apply equally well to languages like TypeScript, Go, Rust, Python, and Kotlin using functional or modular paradigms.
Does clean architecture require dependency injection?
Not strictly. While dependency injection is a very convenient way to pass implementations to abstractions, the primary requirement is applying the principle of dependency inversion rather than using a specific DI framework.
Does clean architecture make testing easier?
Yes, significantly. Because core business logic does not depend directly on databases, web servers, or external services, developers can write fast unit tests without complex environment setup.
Is clean architecture good for small applications?
Not always. For instance, small CRUD tools or quick prototypes may not benefit from extra layers. Therefore, apply clean architecture selectively where the long-term value outweighs the initial complexity.
What is the difference between clean architecture and clean code?
Clean code focuses on readable, maintainable code within individual functions and classes. In contrast, clean architecture focuses on higher-level system structure, layer boundaries, and dependency management.
Here is the rewritten and formatted References section, complete with clear descriptions, authors, and direct hyperlinks to the underlying primary sources.
Here is the corrected References & Further Reading section. The anchor texts have been updated so that neither the exact keyphrase (“Clean Architecture”) nor its direct synonyms are used as anchor text, resolving the SEO competing links warning:
References & Further Reading
-
Robert C. Martin’s Original Clean Coder Article — Robert C. Martin (Clean Coder Blog)
The foundational 2012 blog post introducing this software design model, the famous concentric circle diagram, and the core Dependency Rule.
-
Microsoft’s Architectural Guidance and Patterns — Microsoft Learn
An in-depth guide to modern application design patterns, covering monolithic architectures, traditional multi-tier designs, application boundaries, dependency inversion, and testability.
-
AWS Prescriptive Guidance on Hexagonal Patterns — AWS Prescriptive Guidance
Amazon Web Services’ pattern reference explaining Ports and Adapters, decoupling domain logic from infrastructure, reducing technology lock-in, and simplifying testing.
-
AWS Cloud Implementation Strategies — AWS Prescriptive Guidance
Practical implementation strategies covering Domain-Driven Design (DDD), boundary isolation, maintainability, managing change, and scaling microservices on cloud infrastructure.
-
AWS Best Practices for Decoupled Systems — AWS Prescriptive Guidance
Key technical guidance for modeling business domains, structuring projects, automating unit testing, implementing CQRS, and maintaining clear component boundaries.
-
Baeldung Spring Boot Hands-on Walkthrough — Baeldung
A step-by-step practical code walkthrough implementing Entities, Use Cases, Adapters, and Frameworks inside a modern Java and Spring Boot application.

