Skip to content

Clean Architecture — Robert Martin's Dependency Rule (2026)

DodaTech Updated 2026-06-20 7 min read

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

  1. Entities — Enterprise-wide business rules. These are the most stable, least likely to change. A User entity with validation rules, or an Order with pricing logic, belongs here.

  2. Use Cases — Application-specific business rules. Orchestrate the flow of data between entities. A CreateOrderUseCase or AuthenticateUserUseCase defines what the application does, not how.

  3. 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).

  4. 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

Is Clean Architecture the same as Hexagonal Architecture?

Clean Architecture and Hexagonal Architecture share the same core principle — dependency inversion with business logic at the center. Clean Architecture formalizes four specific layers (Entities, Use Cases, Adapters, Frameworks), while Hexagonal defines ports and adapters more generally.

Can I use Clean Architecture with a monolith?

Yes. Clean Architecture works for monoliths, modular monoliths, and microservices. The layers are logical boundaries, not deployment boundaries. Each microservice can internally follow Clean Architecture principles.

Do I need an interface for every dependency?

Only at architectural boundaries. If a use case depends on a repository, the repository interface lives in the use case layer while the implementation lives in the adapter layer. Internal helpers within a layer don't need separate interfaces.

What is the difference between entities and domain objects?

Entities are domain objects that have an identity (an ID field) and encapsulate critical business rules. Domain objects without identity (like a Money value object) also live in the entity layer. The layer is sometimes called the "domain layer" instead of "entities".

How do I handle database transactions in Clean Architecture?

Transactions are infrastructure concerns. The use case defines what should be atomic, and the outer layer (interface adapter or framework) implements the transaction boundary. Use the Unit of Work pattern, with the transaction management pushed to the outermost layer

Practice Questions

  1. What is the Dependency Rule, and why must outer layers depend on inner layers (not the reverse)?

  2. Name the four layers of Clean Architecture from innermost to outermost.

  3. How does dependency inversion help when migrating from PostgreSQL to MongoDB?

  4. Why should the use case layer not import framework-specific classes?

  5. 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