A learning path ready to make your own.

Clean Architecture

Clean Architecture — Concise Summary Clean Architecture is an architectural paradigm that emphasizes separation of concerns, testability, maintainability, and independence from frameworks, UI, databases and other infrastructure. It organizes code into concentric layers with dependencies pointing inward so core business rules remain stable amid technology change. Origins Synthesizes ideas from Layered Architecture, Hexagonal (Ports & Adapters), Onion Architecture, Domain-Driven Design, and SOLID principles. Popularized by Robert C. Martin (“Uncle Bob”) as a concentric-layer model with a Dependency Rule. Core principles Separation of concerns — keep domain logic separate from UI and infra. Dependency Rule — source dependencies point inward; inner layers know nothing about outer layers. Dependency Inversion — high-level modules depend on abstractions defined by the core; implementations live outside. SRP (single responsibility), testability, and explicit boundaries (ports/interfaces). Layers (center → outer) Entities — enterprise business rules, domain objects and invariants. Use Cases / Interactors — application business rules, orchestrate entities to perform operations. Interface Adapters — controllers, presenters, gateways that adapt external data to/from the core. Frameworks & Drivers — web frameworks, DBs, third-party libraries (outermost, replaceable). Mechanics: Dependency Rule & DIP Inner layers declare interfaces (e.g., IOrderRepository); outer layers implement them and are injected at runtime. Enables replacing infra, easier unit testing, and inversion-of-control via DI containers or manual composition roots. Typical project organization Horizontal (by layer): entities, usecases, adapters, framework, composition root. Vertical (feature-first): co-locate entities, usecases, adapters per feature — often better for large codebases. Implementations (illustrative) TypeScript/Node and Python examples follow the same pattern: core interfaces and entities, use cases depend on interfaces, adapters implement persistence and controllers, composition root wires dependencies. Controller/adapters map I/O; use cases contain business logic; repositories abstract persistence. Testing strategies Unit tests for entities/use cases by mocking adapters. Integration tests for adapters with test DBs or lightweight containers. End-to-end tests for full flows; contract tests for microservices. Keep core logic fast, deterministic and independent from infra. Where it fits Best for complex domains, long-lived products, systems that must evolve (e.g., banking, healthcare, e‑commerce). Maps well to microservices, serverless functions, event-driven systems and cloud-native architectures. Benefits, trade-offs & anti-patterns Benefits: testability, replaceability, maintainability, protection from tech churn. Costs: initial complexity, more boilerplate, learning curve; risk of over-engineering for small apps. Anti-patterns: anemic domain model, framework-driven design, violating dependency rule, unnecessary deep layering. Migration strategies for legacy systems Incremental approach: Strangler Fig pattern, extract a small core, add interfaces around external dependencies, use a composition root to swap implementations. Avoid big-bang refactors; keep tests in place to protect behavior. Variants and common patterns Hexagonal (Ports & Adapters), Onion, vertical feature modules, CQRS, Event Sourcing — all compatible variants emphasizing boundaries and ports. Practical checklist / recommended practices Domain-first: entities encapsulate rules; controllers orchestrate only mapping to use cases. Define interfaces in core; implement in outer layers; compose at startup. Favor small, cohesive modules; prefer feature-based modules for large systems. Invest in tests and document boundaries to prevent erosion. Future outlook Core principles remain valuable as platforms evolve. Expect continued adoption with opinionated frameworks (e.g., NestJS, Micronaut), more feature-based modularization, and increased focus on boundaries in distributed/event-driven systems. Further reading Clean Architecture — Robert C. Martin Domain-Driven Design — Eric Evans Patterns of Enterprise Application Architecture — Martin Fowler Hexagonal Architecture (Alistair Cockburn), Onion Architecture (Jeffrey Palermo) Bottom line: Clean Architecture is about establishing clear, durable boundaries and directing dependencies inward so business rules remain robust and testable. Apply it pragmatically—enough structure to protect the core, but not so much that you over-engineer simple cases.

Let the lesson walk with you.

Podcast

Clean Architecture podcast

0:00-2:53

Follow the trail that experts already trust.

Resources

Turn quick sparks into lasting recall.

Flashcards

Clean Architecture flashcards

16 cards

Question

Click to flip
Answer

Prove the idea before it slips away.

Quizzes

Clean Architecture quiz

12 questions

Who is most associated with popularizing the explicit "Clean Architecture" concentric-layer model and the Dependency Rule in modern writings and the 2017 book "Clean Architecture"?

Read deeper, connect wider, own the subject.

Deep Article

Clean Architecture — A Comprehensive Guide

Clean Architecture is a software architecture paradigm that emphasizes separation of concerns, maintainability, testability, and independence from frameworks, UI, databases, and infrastructure. It provides a set of organizing principles and structural guidelines that help teams build systems that are easier to understand, change, and evolve.

This article is a deep dive: history and roots, core principles and theoretical foundations, architecture components and boundaries, practical patterns and implementations, testing and deployment implications, trade-offs and pitfalls, modern adaptations, and a practical example you can use as a starting point.


Contents

  • History and intellectual roots
  • Key concepts and principles
  • The Clean Architecture layers and components
  • Dependency Rule and Dependency Inversion
  • Typical folder/module layouts (examples)
  • Example implementations (TypeScript/Node and Python)
  • Testing strategies
  • Practical applications and real-world usage
  • Benefits, trade-offs and anti-patterns
  • Transitioning legacy systems & migration strategies
  • Clean Architecture in modern contexts: microservices, serverless, event-driven systems
  • Checklist and recommended practices
  • Further reading and resources

1. History and intellectual roots

Clean Architecture did not emerge in isolation. It synthesizes earlier architectural ideas and software engineering principles:

  • Layered Architecture (traditional n-tier designs) — long used in enterprise systems.
  • Hexagonal Architecture / Ports and Adapters — Alistair Cockburn (mid-2000s): decouples application core from external concerns via ports (interfaces) and adapters (implementations).
  • Onion Architecture — Jeffrey Palermo (around 2008): concentric layers with domain model at the center, infrastructure on the outside.
  • Domain-Driven Design (Evans, 2003): focus on a rich domain model and ubiquitous language.
  • SOLID principles (Robert C. Martin and others): single responsibility, open/closed, Liskov substitution, interface segregation, dependency inversion.
  • Clean Architecture — Robert C. Martin (Uncle Bob) popularized the explicit Clean Architecture concentric-layer model and Dependency Rule in his 2012–2017 writings and book "Clean Architecture" (2017).

Clean Architecture is, in effect, an evolution and synthesis of these ideas designed to make systems resilient to change and technology churn.


2. Key concepts and principles

These are the cardinal ideas underlying Clean Architecture:

  • Separation of concerns: isolate business rules from UI, infrastructure, and frameworks.
  • Independence from frameworks: the domain and use cases should not depend on frameworks (so frameworks can be swapped or upgraded).
  • Dependency Rule: source code dependencies must always point inward (toward higher-level policies). Nothing in an inner circle should know about anything in an outer circle.
  • Dependency Inversion (DIP): high-level modules should not depend on low-level modules; both should depend on abstractions.
  • Single responsibility (SRP): each component/module should have one reason to change.
  • Testability: business rules and use cases should be unit-testable without the infrastructure (databases, UI, web servers).
  • Boundaries via interfaces (ports): outer layers implement interfaces defined by inner layers to allow inversion and isolation.
  • Entities and use cases: entities represent enterprise-wide business rules; use cases implement application-specific business rules.
  • Independent of UI and database: the same core can be used with different UIs and persistence mechanisms.

3. The Clean Architecture layers and components

A common depiction is concentric circles. From center to outermost:

  1. Entities (Enterprise Business Rules)
  • Domain objects, business rules, invariants.
  • Highly stable, independent of technology.
  • Example: Customer, Invoice, Order entities and their validation/compute logic.
  1. Use Cases / Interactors (Application Business Rules)
  • Application-specific logic—coordinating entities to accomplish tasks.
  • Orchestrates actions, enforces policies, input/output boundaries.
  • Example: CreateOrderUseCase, AuthenticateUser, CalculateInvoice.
  1. Interface Adapters / Presenters / Controllers / Gateways
  • Adapt data from external forms (DB, web requests, UI) into the format required by use cases/entities.
  • Implement interfaces defined by inner layers (e.g., repository interfaces).
  • Presenters/Controllers format data for the UI.
  1. Frameworks & Drivers (External Interfaces)
  • Web frameworks, UI, DB, third-party libraries, devices.
  • These are the least stable and most changeable parts and should be isolated from the domain.

Key relationships:

  • Inner layers define interfaces; outer layers implement those interfaces.
  • Data flows inwards for requests; outputs flow outwards through presenters/adapters.

Common components and terms:

  • Controller: receives input (e.g., HTTP), maps to input model, passes to use case.
  • Interactor / Use Case: performs business logic for a given application operation.
  • Presenter/ViewModel: prepares output data structures for the UI.
  • Gateway / Repository: abstracts persistence and external service calls.

4. The Dependency Rule and Dependency Inversion

The Dependency Rule: source code dependencies can only point inwards. No inner layer depends on outer layers. This is enforced by programming to interfaces or abstractions.

Dependency Inversion Principle (DIP) is the mechanism:

  • Inner layer defines an abstraction (e.g., interface IOrderRepository).
  • Outer layer provides concrete implementation (e.g., SqlOrderRepository) and is injected at runtime.
  • High-level policies (use cases) depend on abstractions, not concrete implementations.

Benefits:

  • Replace databases, frameworks, UIs with minimal or no change to core logic.
  • Easier to unit test the use cases by mocking the abstractions.

Implementation mechanisms:

  • Interfaces in statically typed languages (Java, C#, TypeScript).
  • Protocols or duck-typing in dynamically typed languages (Python, Ruby) combined with dependency injection.
  • Inversion-of-Control (IoC) containers or manual dependency injection at composition root.

5. Typical folder/module layouts

There are many valid ways to organize code. Two common approaches: vertical by feature and horizontal by layer.

Example horizontal (conventional Clean Architecture):

/src /entities Order.ts Customer.ts /usecases CreateOrder.ts GetOrder.ts /adapters /controllers OrderController.ts /presenters OrderPresenter.ts /gateways OrderRepositoryImpl.ts /framework /db PostgresClient.ts /web ExpressServer.ts /config CompositionRoot.ts

Example vertical (feature-first), sometimes simpler for large systems:

/src /orders entities/Order.ts usecases/CreateOrder.ts adapters/controllers/OrderController.ts adapters/gateways/OrderRepositoryImpl.ts presenters/OrderPresenter.ts /users ...

Feature-first often scales better in large codebases by co-locating feature code.


6. Example implementations

Below are simplified examples showing the pattern in two languages.

Note: these are illustrative snippets, not complete production apps.

A. TypeScript / Node (simplified)

Folder structure:

  • src/
  • entities/Order.ts
  • usecases/CreateOrder.ts
  • interfaces/IOrderRepository.ts
  • adapters/OrderRepositoryPg.ts
  • controllers/OrderController.ts
  • composition/CompositionRoot.ts

IOrderRepository.ts ``ts export interface IOrderRepository { save(order: Order): Promise ; findById(id: string): Promise ; } ``

Order.ts ``ts export class Order { constructor(public id: string, public items: {sku: string, qty: number}[]) {} total() { / business logic / } } ``

CreateOrder.ts (use case) ```ts import { IOrderRepository } from '../interfaces/IOrderRepository'; import { Order } from '../entities/Order';

export class CreateOrder { constructor(private orderRepo: IOrderRepository) {}

async execute(input: {id: string, items: any[]}) { const order = new Order(input.id, input.items); // domain validations await this.orderRepo.save(order); return { success: true, orderId: order.id }; } } ```

OrderRepositoryPg.ts (outer layer persistence) ``ts import { IOrderRepository } from '../interfaces/IOrderRepository'; import { Order } from '../entities/Order'; export class OrderRepositoryPg implements IOrderRepository { constructor(private pgPool: any) {} async save(order: Order) { // convert order -> db record, call pg client } async findById(id: string) { / ... / } } ``

CompositionRoot.ts ```ts import { CreateOrder } from '../usecases/CreateOrder'; import { OrderRepositoryPg } from '../adapters/OrderRepositoryPg';

const pgPool = createPgPool(); const orderRepo = new OrderRepositoryPg(pgPool); export const createOrderUseCase = new CreateOrder(orderRepo); ```

Controller (adapter) ``ts import { createOrderUseCase } from '../composition/CompositionRoot'; export async function postOrder(req, res) { const result = await createOrderUseCase.execute(req.body); res.json(result); } ``

B. Python (illustrative)

interfaces.py ```py from abc import ABC, abstractmethod

class OrderRepository(ABC): @abstractmethod def save(self, order): pass @abstractmethod def get(self, id): pass ```

entities.py ``py class Order: def init(self, id, items): self.id = id self.items = items def total(self): ... ``

usecases.py ```py class CreateOrder: def init(self, repo: OrderRepository): self.repo = repo def execute(self, orderdata): order = Order(orderdata['id'], order_data['items'])

validations...

self.repo.save(order) return {'order_id': order.id} ```

adapters.py ```py class PostgresOrderRepository(OrderRepository): def init(self, db): self.db = db def save(self, order):

transform and persist

pass ```

app.py (composition root) ```py db = connectdb() repo = PostgresOrderRepository(db) createorder = CreateOrder(repo)

wire to Flask route

@app.route('/orders', methods=['POST']) def createorderroute(): return jsonify(create_order.execute(request.json)) ```


7. Testing strategies

Because of the separation of concerns and the Dependency Rule, testing is straightforward and effective.

Types of tests:

  • Unit tests: test entities and use cases in isolation by mocking dependencies (repositories, external services).
  • Integration tests: test interactions with infrastructure (DB, message brokers) with lightweight containers or test doubles.
  • Acceptance/end-to-end tests: verify system behavior via HTTP endpoints or UI automation.
  • Contract tests: useful in microservices to ensure adapters meet expectations.

Guidelines:

  • Test use cases without needing a database by mocking repositories.
  • Use in-memory fakes or test doubles for fast integration tests.
  • Keep tests fast and deterministic — core logic should be pure and not ...

Ready to see the full tree?

Clone the preview to open the complete learning structure, practice tools, and generated study materials.