Clean Architecture — Robert Martin's Dependency Rule (2026)
In this tutorial, you'll learn Clean Architecture's dependency rule — how concentric layers keep business logic independent of frameworks, databases, and UI. Why does this matter? Every framework and database becomes obsolete, but your business rules should outlast them all. Real-world use: enterprise applications at Amazon, Uber, and Spotify use layered architectures inspired by Clean Architecture to survive technology migrations without rewriting core logic.
What Is Clean Architecture?
Clean Architecture, introduced by Robert C. Martin (Uncle Bob), organizes code into concentric circles. The inner circles contain enterprise business rules (Entities), the middle circles hold application-specific use cases, the outer circles provide interface adapters (controllers, presenters, gateways), and the outermost circle contains frameworks and drivers.
The cardinal rule is the Dependency Rule: source code dependencies can only point inward. Nothing in an inner circle can know about anything in an outer circle.
graph TD
subgraph Frameworks
subgraph InterfaceAdapters
subgraph UseCases[Use Cases]
subgraph Entities
style Entities fill:#E74C3C,color:#fff
end
style UseCases fill:#E67E22,color:#fff
end
style InterfaceAdapters fill:#2ECC71,color:#fff
end
style Frameworks fill:#4A90D9,color:#fff
end
Entities -->|Dependency direction| UseCases
UseCases -->|Dependency direction| InterfaceAdapters
InterfaceAdapters -->|Dependency direction| Frameworks
How Clean Architecture Works
The Four Layers
Entities — Enterprise-wide business rules. These are the most stable, least likely to change. A
Userentity with validation rules, or anOrderwith pricing logic, belongs here.Use Cases — Application-specific business rules. Orchestrate the flow of data between entities. A
CreateOrderUseCaseorAuthenticateUserUseCasedefines what the application does, not how.Interface Adapters — Convert data between use cases and external formats. Controllers, presenters, and repository implementations belong here. This layer contains ports (interfaces) and adapters (implementations).
Frameworks and Drivers — The outermost layer. Databases, web frameworks, UI frameworks, device drivers. This layer is replaceable without affecting business rules.
Dependency Inversion in Practice
The outer layer depends on the inner layer through interfaces defined by the inner layer:
// Entity layer — no external dependencies
export class User {
constructor(
public readonly id: string,
public readonly email: string,
private passwordHash: string
) {}
verifyPassword(password: string): boolean {
return compareHash(password, this.passwordHash);
}
}
// Use case layer — depends on entities and an interface (port)
export interface UserRepository {
findById(id: string): Promise<User | null>;
save(user: User): Promise<void>;
}
export class LoginUseCase {
constructor(private userRepo: UserRepository) {}
async execute(email: string, password: string): Promise<User> {
const user = await this.userRepo.findByEmail(email);
if (!user || !user.verifyPassword(password)) {
throw new Error("Invalid credentials");
}
return user;
}
}
// Interface adapter — implements the port
import { UserRepository } from "../use-cases/ports";
import { User } from "../entities/user";
import { Database } from "../frameworks/database";
export class PostgresUserRepository implements UserRepository {
constructor(private db: Database) {}
async findById(id: string): Promise<User | null> {
const row = await this.db.query("SELECT * FROM users WHERE id = $1", [id]);
return row ? new User(row.id, row.email, row.password_hash) : null;
}
async save(user: User): Promise<void> {
await this.db.query("INSERT INTO users ...", [/* ... */]);
}
}
Data Flow
sequenceDiagram
participant W as Web Framework
participant C as Controller
participant UC as Use Case
participant E as Entities
participant R as Repository
W->>C: HTTP Request
C->>UC: Execute command (DTO)
UC->>E: Load entity
UC->>R: Persist changes
R-->>UC: Confirmation
UC-->>C: Result
C-->>W: HTTP Response
Real-World Examples
Python Implementation
A Flask or FastAPI application with services layer as use cases, SQLAlchemy repositories as adapters, and Pydantic models for DTOs. The use cases never import Flask or SQLAlchemy directly.
TypeScript / Node.js
NestJS with modules organized by domain. Controllers (adapters) call services (use cases) that operate on entities. TypeORM repositories implement interfaces defined by the domain layer.
Java / Spring Boot
Spring Boot with a "domain" package containing entities and use cases, an "infrastructure" package with JPA repositories and REST controllers, and an "application" package for configuration.
Pros and Cons
| Pros | Cons |
|---|---|
| Framework independence — swap frameworks without rewriting business logic | Boilerplate — interfaces, DTOs, and mappers add code |
| Testability — test use cases without a database or web server | Complexity — more layers than simple apps need |
| Maintainability — changes in one layer don't ripple through others | Learning curve — team must understand dependency inversion |
| Technology migration — upgrade databases or frameworks safely | Over-engineering risk — premature abstraction for CRUD apps |
| Parallel development — teams work on layers independently | Performance concerns — data transformation between layers adds overhead |
When to Use Clean Architecture
Clean Architecture is ideal for:
- Long-lived applications — products that will outlast their initial framework choices
- Complex business logic — domains with rules that change frequently
- Multiple delivery mechanisms — same use cases exposed via REST, GraphQL, and CLI
- Enterprise teams — multiple developers work on the same codebase
Skip Clean Architecture for prototypes, small CRUD applications, scripts, or projects with a single developer and short expected lifespan.
FAQ
Related Concepts
- Hexagonal Architecture — ports and adapters pattern
- MVC Architecture — controllers as interface adapters
- Dependency Injection — wiring layers together
- CQRS Pattern — separate command/query models in use cases
Practice Questions
What is the Dependency Rule, and why must outer layers depend on inner layers (not the reverse)?
Name the four layers of Clean Architecture from innermost to outermost.
How does dependency inversion help when migrating from PostgreSQL to MongoDB?
Why should the use case layer not import framework-specific classes?
What belongs in the Entities layer versus the Use Cases layer?
Challenge
Take an existing controller that directly calls a database and renders HTML. Decompose it into Clean Architecture layers: an Entity (business object), a Use Case (orchestrates logic), an Interface Adapter (controller converts HTTP to use case input), and a Framework (Django/Express handler).
Real-World Task
Open an existing project. Create a domain/ directory with entities and use case interfaces. Move business logic out of controllers and into use cases. Confirm the use cases have zero imports from your web framework or ORM. Run your tests — they should pass without a database connection.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro