Skip to content
Feb 28

Clean Architecture

MT
Mindli Team

AI-Generated Content

Clean Architecture

Creating software that remains adaptable over years, survives technology shifts, and can be validated without complex setup is the holy grail of software engineering. Clean Architecture is a design philosophy that makes this achievable by enforcing a simple, powerful rule: keep your core business logic pure and isolated from the volatile details of frameworks, databases, and user interfaces. It organizes an application into concentric layers, ensuring that changes to external concerns do not force costly rewrites of your most valuable asset—the business rules.

The Dependency Rule: The Core Principle

At the heart of Clean Architecture lies a single, non-negotiable directive known as The Dependency Rule. It states that source code dependencies must point only inward, toward higher-level policies. This means that nothing in an inner circle can know anything at all about something in an outer circle. In practical terms, your business logic (the inner circles) must not import or depend on classes from your web framework, your database ORM, your UI toolkit, or any other external agency.

This rule is enforced through techniques like dependency inversion, where inner layers define abstract interfaces for the operations they need, and outer layers provide concrete implementations that adhere to those interfaces. For example, a use case that needs to save data will define a Repository interface. A database adapter in an outer layer will then implement that interface. The use case depends on the abstraction (the interface), not the concrete database code. This inversion of control is what makes the core independent and easily testable—you can swap a real database with an in-memory test double without touching a single line of business logic.

The Concentric Layers of Responsibility

Clean Architecture visualizes the system as a set of concentric circles, each representing a different area of software. The outermost layers are mechanisms; the innermost are policies.

Entities (The Innermost Layer)

Entities encapsulate enterprise-wide business rules. They are the fundamental objects of your domain—like Customer, Account, or Invoice—complete with their data and the critical rules that govern them. These objects should be entirely plain and free of any dependency on frameworks or external concerns. An Invoice entity, for instance, would know how to calculate its total and validate its line items, but it would have no knowledge of SQL, HTTP, or file systems. Their stability is paramount; they change only when the core business rules change.

Use Cases (The Application Business Rules Layer)

Surrounding the entities is the use cases layer. This layer contains application-specific business logic. It orchestrates the flow of data to and from the entities, directing them to work together to achieve a specific goal. A use case like ProcessOrder would coordinate retrieving an Order entity, validating inventory via a Product entity, calculating totals, and instructing a repository to persist the result. Crucially, use cases are also isolated from external details. They define the boundaries—the interfaces—through which they interact with the outside world (e.g., a DataRepository interface or a PaymentGateway interface) but contain no implementation details.

Interface Adapters (The Adapter Layer)

This layer is a set of adapters that convert data between the convenient format for the use cases and entities, and the convenient format for external agencies like databases or the web. It includes:

  • Controllers: Convert HTTP requests into calls to use cases, and convert use case results back into HTTP responses.
  • Presenters/View Models: Format data from use case output into a form suitable for the UI.
  • Gateways/Repositories: Implement the interfaces defined by the use case layer, using concrete frameworks. For example, a SqlUserRepository class implements the UserRepository interface and contains the actual SQL queries.

This layer is where you’ll find your MVC controllers, but they are thin, humble objects that merely adapt and delegate.

Frameworks and Drivers (The Outermost Layer)

This is where all the details live: the web framework (Spring, Rails, Express), the database (PostgreSQL, MongoDB), the UI framework (React, Angular), and any external APIs or services. This layer is the most volatile and is considered a plugin to the core application. The inner layers do not depend on it; instead, it depends on the interfaces defined by the inner layers.

Implementing Boundaries and Testing

The true power of this architecture is revealed during testing and maintenance. Because your business rules—the entities and use cases—have zero dependencies on external frameworks, they can be unit tested in complete isolation. You can test the ProcessOrder use case by passing in mock implementations of its required repositories and gateways, running complex business scenarios in milliseconds without starting a web server or database.

The boundaries between layers are maintained by leveraging the dependency inversion principle and paying careful attention to the direction of data flow. Data should cross boundaries as simple, independent data structures—not as framework-dependent objects like Active Record models or HttpRequest objects. This prevents framework "leak" into your core. For instance, a controller receives an HTTP request, builds a plain OrderRequest data object from it, and passes that object to the use case. The use case returns a plain OrderResult object, which the presenter then formats for the view.

Common Pitfalls

  1. Leaking Framework Details Inward: The most common mistake is allowing a framework's object model to penetrate the use case or entity layers. For example, using a JPA @Entity annotation inside your domain User class or returning an Express Response object from a use case. This tightly couples your business logic to that specific framework, violating the core dependency rule. The fix is to maintain separate, plain models for your domain and use mapping code (in the interface adapters layer) to convert to and from framework-specific models.
  1. Over-Engineering for Simple Projects: Clean Architecture introduces indirection and more boilerplate code (interfaces, adapters, mappers). For a simple CRUD application or a short-lived prototype, this overhead may not be justified. The pitfall is applying it dogmatically everywhere. The correction is to assess the complexity and expected lifespan of the project. Start with a simpler structure and refactor toward cleaner boundaries as complexity grows.
  1. Misunderstanding Layer Responsibilities: Developers often place business logic in controllers or database repositories, turning the use case layer into a hollow pass-through. This leads to an Anemic Domain Model, where entities are mere data bags with no behavior. The correction is to vigilantly ask, "Is this rule about how something is done (a detail for adapters) or what should be done (a policy for use cases/entities)?" Application workflow logic belongs in use cases; fundamental domain rules belong in entities.
  1. Creating Cyclical Dependencies Between Layers: While the rule is "dependencies point inward," it's possible to create subtle circular references, for instance, between use cases or between entities. This makes the code rigid and hard to test. The solution is to apply dependency inversion within the same layer if necessary, or to re-evaluate the responsibilities and introduce a new abstraction to break the cycle.

Summary

  • Clean Architecture organizes software into concentric layers (Entities, Use Cases, Interface Adapters, Frameworks) with a strict Dependency Rule mandating that dependencies point inward, protecting the core business logic.
  • The innermost Entities hold enterprise-wide business rules and data, while Use Cases contain application-specific logic, orchestrating entities to fulfill user goals.
  • The Interface Adapters layer acts as a translator, converting data between the format convenient for the core and the format needed by external frameworks and devices, which reside in the outermost, most volatile layer.
  • This separation enables highly testable business logic that can be validated without external systems and creates a system that is independent of frameworks, UI, and databases, allowing these details to be swapped or updated with minimal impact.
  • Successful implementation requires vigilance to prevent framework details from leaking inward, to apply the pattern judiciously based on project needs, and to ensure all business rules are correctly housed within the appropriate layer.

Write better notes with AI

Mindli helps you capture, organize, and master any subject with AI-powered summaries and flashcards.