Architecture Styles Clean Code

Clean Architecture

Concentric dependency layers organized so that the domain is at the core, use cases in the next ring, and frameworks and drivers on the outermost ring. Dependencies only point inward.

⏱ 9 min read

What it is

Clean Architecture, formalized by Robert C. Martin ("Uncle Bob") in his 2012 blog post and 2017 book, organizes an application into concentric rings with four primary layers: Entities (enterprise business rules), Use Cases (application business rules), Interface Adapters (controllers, presenters, gateways), and Frameworks & Drivers (databases, web frameworks, UI, external APIs). The central rule — the Dependency Rule — states that source code dependencies may only point inward toward higher-level layers. Nothing in an inner circle can know anything about something in an outer circle. This structural constraint, enforced at compile time, prevents framework and infrastructure concerns from contaminating business rules. Use Case Interactors orchestrate entities and call gateway interfaces; they never call concrete repositories or HTTP clients directly.

Why it exists

Martin observed that many architectures place the framework at the architectural center — the Spring Boot application or the Rails app — when the framework is really a delivery mechanism. This leads to business logic scattered across controllers, services shaped by ORM constraints, and tests that cannot run without spinning up the entire framework. Clean Architecture inverts this: the business rules are at the center, and the framework is a plugin. The goals are: independent testability of business rules, independence of the UI, independence of the database, and independence of any external agency. These are the same goals as Hexagonal Architecture, but expressed with more explicit ring decomposition and naming (Entities, Use Cases, Gateways, Presenters).

When to use

  • Complex domain logic that must be isolated from rapidly changing infrastructure and UI frameworks.
  • Long-lived applications where the domain outlives multiple framework generations (Ruby on Rails → Sinatra → API-only, etc.).
  • Teams that practice TDD and want their test suite to be fast — all inner ring tests run without infrastructure.
  • Applications that may expose the same business logic through multiple delivery mechanisms (REST API, CLI, GraphQL, gRPC).
  • Regulated domains where business rules must be auditable in isolation from infrastructure.

When not to use

  • CRUD-heavy applications with little business logic — interactors just delegate to repositories, adding indirection with no value.
  • Prototypes or short-lived scripts where long-term maintainability is not a concern.
  • Teams unfamiliar with dependency inversion — the pattern requires understanding of abstraction and inversion before it can be applied correctly.

Typical architecture

Four rings with inward-only dependencies. The Dependency Rule is the single architectural constraint that defines the pattern.

┌─────────────────────────────────────────────┐
│  Frameworks & Drivers (outermost)            │
│  (Spring, Express, Postgres, React, Kafka)  │
│  ┌───────────────────────────────────────┐  │
│  │  Interface Adapters                    │  │
│  │  (Controllers, Presenters, Gateways)  │  │
│  │  ┌─────────────────────────────────┐  │  │
│  │  │  Use Cases (Interactors)         │  │  │
│  │  │  ┌───────────────────────────┐  │  │  │
│  │  │  │   Entities                 │  │  │  │
│  │  │  │   (Domain Objects,         │  │  │  │
│  │  │  │    Business Rules)         │  │  │  │
│  │  │  └───────────────────────────┘  │  │  │
│  │  └─────────────────────────────────┘  │  │
│  └───────────────────────────────────────┘  │
└─────────────────────────────────────────────┘
  Dependencies only point INWARD →

Pros and cons

Pros

  • Business rules are completely isolated and testable without any framework or infrastructure.
  • The database, web framework, and external services are details that can be swapped freely.
  • Explicit naming of architectural roles (Interactor, Gateway, Presenter) creates a shared vocabulary.
  • Multiple delivery mechanisms (REST, gRPC, CLI) can share the same use case layer.
  • Strong alignment with SOLID principles, especially Dependency Inversion and Single Responsibility.

Cons

  • Significant boilerplate — each use case requires an Input Port, Output Port, Interactor, Request Model, and Response Model.
  • Over-engineered for simple CRUD; mapping between layers adds work that provides no value when there are no business rules to protect.
  • Presenter pattern (pushing output through an Output Port) is unfamiliar and awkward in synchronous REST contexts.
  • Can result in many small classes that are individually simple but collectively hard to navigate without a clear naming convention.

Implementation notes

Start by defining use cases as interfaces (Input Ports) that the controller calls and as Output Ports that the Interactor calls back with results. The Interactor is the only class that implements the use case business logic — it receives a Request Model (not a web framework DTO), executes domain logic using Entities, calls Repository gateways (interfaces), and passes a Response Model to the Output Port (Presenter). Wire everything with dependency injection at the composition root. Keep Request Models and Response Models as plain data objects (no annotations, no framework types). In practice, many teams simplify by omitting the Presenter and having the Interactor return a Response Model directly — this loses the strict MVC compliance but is far more pragmatic.

Common failure modes

  • Skipping the Request/Response Model mapping: Passing HTTP request DTOs directly into the Use Case, coupling the use case layer to the web framework.
  • Framework annotations in Entities: JPA, Jackson, or Lombok annotations on domain entities — violates the Dependency Rule at its core.
  • Anemic interactors: Interactors that just call one repository method — adds four indirection layers for a `findById` with no business rules.
  • Repository implementations in the wrong layer: Concrete repository classes placed in the Use Case layer rather than the Interface Adapters layer.
  • Skipping architecture tests: Without automated enforcement, inner rings accumulate outer-ring imports within months of the initial implementation.

Decision checklist

  • Can every Use Case Interactor be tested with in-memory gateway implementations, no database required?
  • Are Entities free of all framework imports (JPA, Spring, Jackson)?
  • Do Request Models and Response Models contain only plain data — no web framework types?
  • Are Gateway interfaces defined in the Use Cases layer, not in the Infrastructure layer?
  • Are architecture tests (ArchUnit or equivalent) enforcing the Dependency Rule in CI?
  • Is there a clear composition root responsible for wiring all dependencies?

Example use cases

  • Multi-channel fintech platform: The same loan approval use case runs via REST API, mobile app, and batch job — different controllers, same Interactor with the same business rules.
  • Healthcare records system: Clinical domain rules encoded in Entities are completely isolated from EHR database specifics; swapping from Oracle to PostgreSQL touches only Gateway adapters.
  • Enterprise SaaS with complex business logic: Subscription billing, usage metering, and entitlement rules live in Entities and Use Cases, independently auditable from any web framework.
  • Hexagonal Architecture — The ports-and-adapters formulation of the same dependency inversion principle; less prescriptive ring decomposition.
  • Onion Architecture — Jeffrey Palermo's variant; similar concepts but with explicit Domain Services and Application Services layers.
  • CQRS — Frequently applied inside the Use Cases layer to separate command and query interactors.

Further reading