Clean Architecture
Clean Architecture
"The Dependency Rule: Source code dependencies can only point inwards. Nothing in an inner circle can know anything about something in an outer circle." — Robert C. Martin, 2012
Intent
Clean Architecture is Robert C. Martin's 2012 formulation of the dependency inversion principle applied to application architecture. The Dependency Rule is the ONLY rule — everything else (four concentric circles, specific layer names) is illustrative, not prescriptive. The rule is: source code dependencies must point inward.
Clean, Hexagonal, and Onion express the same foundational principle with different vocabularies — three separate notes exist because teams encounter these patterns by name and the vocabulary differences matter in practice. A developer working in a codebase that uses the term "Use Cases" needs the Clean Architecture vocabulary, not a merged description.
Martin's companion concept — "Screaming Architecture" — holds that package names should scream use cases, not frameworks. com.app.ordering communicates domain intent; com.app.services communicates nothing. The folder/package structure is itself a design decision.
When NOT to Use
- Simple CRUD applications with no significant business rules — the Use Case abstraction overhead exceeds its value when there is no business logic to isolate
- Small scripts and utilities
- Teams early in DDD adoption who have not yet identified bounded contexts — the Entities vs Use Cases distinction is only meaningful once domain boundaries are clear
- Prototypes where the cost of layer separation outweighs the benefit of testability
When to Use
- Systems with non-trivial business rules that must be tested independently of the UI and database
- Teams that need to swap persistence or delivery mechanisms without rewriting business logic
- When "screaming architecture" is desired — a package structure that communicates use cases, not frameworks
- Long-lived systems where framework churn is expected (frameworks change; business rules should not)
How It Works
Clean Architecture defines four concentric layers (inner to outer):
- Entities — enterprise-wide business rules; pure domain objects. An
Orderthat validates its own invariants is an Entity. This rule applies in any application using this domain — it is not specific to one workflow. - Use Cases — application-specific business rules; orchestrate Entities to fulfil one workflow.
PlaceOrderUseCasecoordinatesOrder,Payment, andInventoryfor THIS application's specific workflow. Use Cases define the interfaces (ports) that outer layers must implement. - Interface Adapters — Controllers, Presenters, Gateways. This layer translates between the format convenient for Use Cases and the format convenient for Frameworks. A Controller translates an HTTP request into a command; a Gateway translates a Use Case's repository interface into SQL.
- Frameworks & Drivers — Web, DB, UI, external services. The outermost ring. All framework coupling lives here. Database drivers, HTTP servers, and UI components are details — they are plugged in from outside.
The Dependency Rule (precise wording): "Source code dependencies may only point inward. Nothing in an inner circle can know anything about something in an outer circle."
This means: Entities know nothing about Use Cases. Use Cases know nothing about Interface Adapters. Interface Adapters know nothing about Frameworks. The Order entity has no import of a JPA annotation. The PlaceOrderUseCase has no import of express.Request. Violations of this rule break the architecture.
+---------------------------------------+
| Frameworks & Drivers |
| +---------------------------+ |
| | Interface Adapters | |
| | +---------------+ | |
| | | Use Cases | | |
| | | +---------+ | | |
| | | | Entities| | | |
| | | +---------+ | | |
| | +---------------+ | |
| +---------------------------+ |
+---------------------------------------+
Dependencies --> inward
All three architectures express the same foundational principle — source code dependencies must point inward toward the domain — but use distinct vocabularies. Teams use specific terminology; the vault must match what they will encounter in any given codebase.
| Dimension | Hexagonal (Cockburn, 2005) | Onion (Palermo, 2008) | Clean (Martin, 2012) |
|---|---|---|---|
| Layer Names | Port / Adapter / Application Core | Domain Model / Domain Services / Application Services / Infrastructure | Entities / Use Cases / Interface Adapters / Frameworks & Drivers |
| Dependency Rule Statement | All outside actors connect via ports; application core has no outward dependencies | All coupling toward the centre; outer layers depend on inner layers | Source code dependencies can only point inward |
| What It Prescribes Inside the Core | Nothing — the hexagon's internals are not specified | Explicitly separates Domain Services from Application Services | Explicitly separates Entities from Use Cases |
Architecture Diagram
Clean-Architecture-diagram.excalidraw
TypeScript Example
Folder layout showing WHERE code lives and which direction dependencies point:
src/
domain/ # Entities (no external imports)
Order.ts
application/ # Use Cases
ports/
OrderRepository.ts # interface — defined here, implemented outside
PlaceOrderUseCase.ts
adapters/ # Interface Adapters
controllers/
OrderController.ts
persistence/
PostgresOrderRepository.ts
infrastructure/ # Frameworks & Drivers
db/
http/
Key interface pattern — the dependency rule enforced by types:
// Clean Architecture — TypeScript
// src/application/ports/OrderRepository.ts
// Interface defined in Use Cases ring — NOT in infrastructure
export interface OrderRepository {
findById(id: string): Promise<Order | null>;
save(order: Order): Promise<void>;
}
// src/application/PlaceOrderUseCase.ts
// Use Case imports ONLY from domain and ports — no framework or DB imports
import { Order } from '../domain/Order';
import { OrderRepository } from './ports/OrderRepository';
export class PlaceOrderUseCase {
constructor(private readonly orders: OrderRepository) {}
async execute(command: PlaceOrderCommand): Promise<void> {
const order = Order.place(command.items, command.customerId);
await this.orders.save(order);
}
}Java Example
Package layout:
com.app/
domain/ # Entities
Order.java
application/ # Use Cases
ports/
OrderRepository.java # interface defined here
PlaceOrderUseCase.java
adapters/ # Interface Adapters
controllers/
OrderController.java
persistence/
JpaOrderRepository.java
infrastructure/ # Frameworks & Drivers
config/
Key interface pattern:
// Clean Architecture — Java
// application/ports/OrderRepository.java — interface in Use Cases ring
public interface OrderRepository {
Optional<Order> findById(OrderId id);
void save(Order order);
}
// application/PlaceOrderUseCase.java — no Spring, no JPA imports
public class PlaceOrderUseCase {
private final OrderRepository orders; // Injected at composition root
public PlaceOrderUseCase(OrderRepository orders) {
this.orders = orders;
}
public void execute(PlaceOrderCommand command) {
Order order = Order.place(command.items(), command.customerId());
orders.save(order);
}
}Enforcement Tooling
Stating the Dependency Rule without automated enforcement leaves it aspirational. Violations accumulate silently. Use these tools to make the rule a failing build check.
TypeScript — dependency-cruiser
npm install --save-dev dependency-cruiser// .dependency-cruiser.js
{
forbidden: [{
name: "no-domain-to-infrastructure",
from: { path: "^src/domain" },
to: { path: "^src/(adapters|infrastructure)" }
}, {
name: "no-application-to-infrastructure",
from: { path: "^src/application" },
to: { path: "^src/infrastructure" }
}]
}Java — ArchUnit
<!-- Maven: pom.xml -->
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>1.3.0</version>
<scope>test</scope>
</dependency>@AnalyzeClasses(packages = "com.app")
class CleanArchitectureTest {
@ArchTest
static final ArchRule domainMustNotDependOnAdapters =
noClasses().that().resideInAPackage("..domain..")
.should().dependOnClassesThat()
.resideInAPackage("..adapters..");
@ArchTest
static final ArchRule applicationMustNotDependOnInfrastructure =
noClasses().that().resideInAPackage("..application..")
.should().dependOnClassesThat()
.resideInAPackage("..infrastructure..");
}Common dependency rule violations to watch for:
- JPA annotations (
@Entity,@Column) placed directly on domain entity classes — these importjavax.persistenceorjakarta.persistenceinto the Entities ring- Framework imports (
import { Injectable } from '@nestjs/common') inside use case classes — the Use Cases ring must not import framework modules- Logging framework imports (
import org.slf4j.Logger) inside domain services — domain logic must not depend on a specific logging implementation- Direct
express.Requestimport inside a use case — HTTP types belong in the Interface Adapters ring, not in Use Cases
Related Concepts
| Pattern | Relationship |
|---|---|
| Hexagonal-Architecture | Cockburn's equivalent formulation (2005) using Port/Adapter vocabulary; both enforce the same Dependency Rule with different layer names |
| Onion-Architecture | Palermo's equivalent formulation (2008) with explicit Domain Services vs Application Services split; the Use Cases layer in Clean maps to two separate rings in Onion |
| Adapter-Pattern | The GoF Adapter is the structural mechanism that Interface Adapters use — each adapter translates a framework type to the interface the Use Cases expect |
| Repository | The canonical dependency inversion example: Repository interface defined in Use Cases ring, implementation in Infrastructure — the same inversion the Dependency Rule enforces throughout |
| Bounded-Context | Bounded Contexts define the domain boundaries within which a Clean Architecture is valid; the Entities and Use Cases layers align with one context's domain model |
| CQRS-Pattern | Commands and Queries map to Use Case classes; CQRS is the application-level decomposition of what Clean Architecture calls the Use Cases layer |
| Facade-Pattern | Interface Adapters expose Facade semantics toward the outer ring — they simplify the Use Cases API for controllers and presenters |
Sources
- Robert C. Martin, "The Clean Architecture" (2012) — https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
- Robert C. Martin, Clean Architecture: A Craftsman's Guide to Software Structure and Design, Prentice Hall, 2017
- Herberto Graca, "DDD, Hexagonal, Onion, Clean, CQRS — How I Put It All Together" (2017) — https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/