November 30, 2025 (4mo ago) — last updated March 12, 2026 (22d ago)

Mastering Clean Architecture (Robert C. Martin)

Practical guide to Robert C. Martin’s Clean Architecture: layers, Dependency Rule, TypeScript example, trade-offs, and when to apply it.

← Back to blog
Cover Image for Mastering Clean Architecture (Robert C. Martin)

Robert C. Martin’s Clean Architecture puts business logic at the centre of your system so it’s protected from changes in frameworks, databases, and UIs. This practical guide explains the layers, the Dependency Rule, trade‑offs, and a TypeScript/Node.js example for a simple “create user” feature.

Clean Architecture Guide — Robert C. Martin

Summary: Practical guide to Robert C. Martin’s Clean Architecture: layers, principles, TypeScript example, trade‑offs, and implementation tips for scalable, testable systems.

Introduction

Robert C. Martin’s Clean Architecture puts business logic at the centre of your system so it’s protected from changes in frameworks, databases, and UIs. This guide explains the concentric layers, the Dependency Rule, trade‑offs you’ll face, and a TypeScript/Node.js example that maps theory to a simple “create user” feature.

What Is Clean Architecture?

Many projects end up with business logic tangled with frameworks and databases, which makes changes risky and expensive. Clean Architecture enforces separation of concerns through concentric layers so the code that makes your application unique—the business rules—doesn’t depend on delivery or infrastructure details. The model is governed by one core constraint: the Dependency Rule—source code dependencies point inward. This rule unlocks practical benefits like framework independence, testability, database flexibility, and UI portability.

A hand-drawn diagram illustrating "Business Rules" as a central house interacting with various external systems and data flows.

The Power of Independence

Because inner layers don’t know about outer layers, you can:

  • Swap web frameworks without touching business logic.
  • Run fast, isolated unit tests that don’t require a database or server.2
  • Move between SQL and NoSQL storage with minimal core changes.
  • Reuse the same core logic for a web app, mobile app, or CLI.

These options let your application evolve rather than forcing early, irreversible choices.

Core Goals of Clean Architecture

GoalDescriptionBusiness Impact
IndependenceDecouple core logic from frameworks, UI, and databases.Reduces vendor lock‑in and supports future technology changes.
TestabilityLet business rules be tested in isolation.Faster feedback and higher confidence in releases.2
MaintainabilityKeep changes localized and safer.Lower long‑term cost of ownership and faster onboarding.3
ScalabilitySeparate concerns so components can evolve independently.Easier to scale parts of the system as load or complexity grows.

These goals make systems resilient and easier to evolve over time.

Standing on the Shoulders of Giants

Robert C. Martin synthesized earlier patterns like Hexagonal (Ports and Adapters) and Onion Architecture to form this unified model; his book and talks made the vocabulary common in modern system design.1

We apply these ideas in production systems and reference materials, for example the Clean Architecture PDF and other guides hosted on our site: Clean Architecture PDF.

Understanding the Layers (Onion Model)

All dependencies point inward. Outer layers know about inner layers; inner layers know nothing about outer layers.

A hand-drawn diagram of Robert C. Martin's Clean Architecture, showing concentric layers from Dependency Rule to Entities.

Layer 1: Entities — Core Business Logic

Entities are pure business objects that contain enterprise‑wide rules. For an e‑commerce system, an Order entity would calculate totals and validate shipping constraints. Entities should be stable and free of framework or persistence concerns.

Layer 2: Use Cases — Application Rules

Use Cases (Interactors) orchestrate Entities to achieve application goals like “Create a User” or “Process a Payment.” They define inputs and outputs and should remain ignorant of delivery mechanisms.

Key point: Entities express enterprise rules; Use Cases express application behavior.

Layer 3: Interface Adapters — Translators

This layer contains controllers, presenters, and repository interfaces that translate data between the outer infrastructure and the inner application. Repositories are defined as interfaces (ports) that Use Cases depend on; concrete adapters live outside the core.

Layer 4: Frameworks & Drivers — Outermost Details

Outermost code is framework and infrastructure glue: web frameworks, databases, UI frameworks, and third‑party libraries. This layer is volatile; keeping it outside the core minimizes the blast radius of changes.

Building a Service with TypeScript and Node.js

The walkthrough below shows how to map the onion layers to a real project for a “create user” feature.

Project Folder Structure

A folder structure that mirrors the layers helps enforce boundaries:

  • src/
    • domain/: Entities
    • application/: Use Cases and ports
    • infrastructure/: Database connections, adapters
    • interfaces/: Controllers, route handlers

Step 1: Defining the User Entity

// src/domain/user.ts

export class User {
  constructor(
    public readonly id: string,
    public name: string,
    public email: string,
    private createdAt: Date,
  ) {}

  public static create(id: string, name: string, email: string): User {
    if (!name || name.length < 2) {
      throw new Error("User name must be at least 2 characters long.");
    }
    return new User(id, name, email, new Date());
  }
}

This entity contains only business rules and no persistence or framework details.

Step 2: Creating the Use Case

Define a repository interface as a port so the use case depends on an abstraction, not a concrete implementation.

// src/application/ports/userRepository.ts

import { User } from '../../domain/user';

export interface UserRepository {
  save(user: User): Promise<void>;
  findByEmail(email: string): Promise<User | null>;
}

Now the use case:

// src/application/createUserUseCase.ts

import { User } from '../domain/user';
import { UserRepository } from './ports/userRepository';

export class CreateUserUseCase {
  constructor(private readonly userRepository: UserRepository) {}

  async execute(name: string, email: string): Promise<User> {
    const existingUser = await this.userRepository.findByEmail(email);
    if (existingUser) {
      throw new Error("User with this email already exists.");
    }
    const userId = 'some-unique-id';
    const newUser = User.create(userId, name, email);
    await this.userRepository.save(newUser);
    return newUser;
  }
}

This use case can be unit‑tested in isolation with a mock repository, which is a major gain of the pattern.2

Step 3: Adapters and Controllers

Implement repository adapters in infrastructure (Prisma, TypeORM, direct SQL, etc.) and expose Use Cases via thin controllers in interfaces.

// src/interfaces/userController.ts

import { Request, Response } from 'express';
import { CreateUserUseCase } from '../application/createUserUseCase';

export class UserController {
  constructor(private readonly createUserUseCase: CreateUserUseCase) {}

  async createUser(req: Request, res: Response): Promise<void> {
    try {
      const { name, email } = req.body;
      const user = await this.createUserUseCase.execute(name, email);
      res.status(201).json(user);
    } catch (error) {
      res.status(400).json({ message: (error as Error).message });
    }
  }
}

The controller is a thin translation layer; the core application has no knowledge of Express or HTTP specifics.

Weighing the Benefits and Trade‑offs

Clean Architecture is a pragmatic investment. It brings long‑term benefits at the cost of initial complexity and some boilerplate.

Upsides

  • Superior testability and faster feedback loops via isolated unit tests.2
  • Freedom from framework lock‑in—core business rules remain stable as technologies change.
  • Better maintainability and onboarding through explicit structure, reducing long‑term technical debt.3

Organizations that invest in modular design and automated testing report fewer production incidents and faster recovery times, which helps justify the initial investment6.

Trade‑offs

  • Higher upfront complexity and a learning curve for teams new to the approach.
  • Potential for boilerplate if applied dogmatically to small projects.

Choose pragmatically: adopt core principles early without enforcing unnecessary ceremony for tiny prototypes.

Decision Matrix: Is Clean Architecture Right for Your Project?

FactorWhen it helpsWhen it’s overkill
Project sizeLarge, long‑lived systemsShort experiments or throwaway prototypes
Team experienceMixed teams; need for clear boundariesSmall, experienced team shipping quickly
VelocitySlower start, safer long‑term changesQuick initial delivery required
MaintainabilityHighRisk of excessive boilerplate

Common Mistakes and How to Avoid Them

Dogmatism and Over‑engineering

Avoid applying every rule to trivial problems. Use the architecture where it reduces risk, not as a religious requirement.

Leaky Abstractions

Don’t let framework or ORM annotations leak into Entities. Keep DTOs and mappers at the boundaries so the core remains pure.

Anemic Domain Model

Keep behaviour inside entities when logic operates on their state. Avoid turning entities into passive data bags.

Practical Questions

How does Clean Architecture relate to Hexagonal or Onion?

They’re close cousins: Hexagonal emphasizes ports and adapters, Onion uses the concentric metaphor, and Clean Architecture unifies these ideas into a clear, four‑layer model. The Dependency Rule is the shared principle.4

How should I handle database transactions?

Wrap Use Cases with a transactional decorator or use a Unit of Work. Both approaches let infrastructure manage transactions without making the core aware of persistence details.5

Is it suitable for small projects?

If the project is truly short‑lived, the overhead can be unnecessary. If you expect the product to evolve, apply the core separation principles and let the architecture grow as needed.

Concise Q&A

Q: What problem does Clean Architecture solve?

A: It protects business rules from technology churn by enforcing an inward dependency flow, making systems more testable and adaptable.

Q: What are the main trade‑offs?

A: Upfront complexity and possible boilerplate versus long‑term maintainability and lower technical debt.

Q: How do I start pragmatically?

A: Separate domain logic from frameworks first. Define ports (interfaces) for infrastructure and add adapters as demand grows.


1.
Robert C. Martin, Clean Architecture: A Craftsman’s Guide to Software Structure and Design. https://www.oreilly.com/library/view/clean-architecture/9780134494272/
2.
Why unit testing matters and benefits for fast feedback loops. https://circleci.com/blog/why-unit-testing/
3.
What is technical debt and how it affects maintainability. https://www.sonarsource.com/resources/technical-debt/
4.
Hexagonal Architecture and its relation to ports and adapters. https://alistair.cockburn.us/hexagonal-architecture/
5.
Unit of Work and transactional patterns for coordinating persistence around use cases. https://martinfowler.com/eaaDev/UnitOfWork.html
6.
State of DevOps and delivery performance research describing benefits of automated testing and modular architecture. https://cloud.google.com/devops/state-of-devops
← Back to blog
🙋🏻‍♂️

AI writes code.
You make it last.

In the age of AI acceleration, clean code isn’t just good practice — it’s the difference between systems that scale and codebases that collapse under their own weight.