November 30, 2025 (2mo ago)

Mastering Robert Martin Clean Architecture

A practical guide to Robert Martin Clean Architecture. Learn its layers, principles, and real-world implementation for building scalable software.

← Back to blog
Cover Image for Mastering Robert Martin Clean Architecture

A practical guide to Robert Martin Clean Architecture. Learn its layers, principles, and real-world implementation for building scalable software.

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 your 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, real-world trade-offs, and a TypeScript/Node.js implementation for 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 a strict 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.

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

The model is governed by one core constraint: the Dependency Rule—source code dependencies must point inwards. This simple constraint unlocks practical benefits like framework independence, testability, database independence, and UI flexibility.

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 advantages give your application the options to evolve rather than forcing early, irreversible decisions.

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.
ScalabilitySeparate concerns so components can evolve independently.Easier to scale parts of the system as load or complexity grows.

These goals work together to make systems resilient and easier to evolve.

Standing on the Shoulders of Giants

Robert C. Martin popularized this unified model by synthesizing earlier patterns like Hexagonal (Ports and Adapters) and Onion Architecture; his book and talks helped make the diagram and vocabulary common in modern systems design.14

We apply these ideas in production systems and reference materials, for example the Clean Architecture PDF and other guides hosted on our site and blog pages (see internal resources: Clean Architecture PDF).

Understanding the Layers (Onion Model)

The architecture uses a concentric 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-specific goals, such as “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, third‑party libraries. This layer is expected to be volatile; placing it outside the core minimizes the blast radius of changes.

Building a Service with TypeScript and Node.js

Below is a practical walkthrough for a “create user” feature showing how to map the onion layers to a real project.

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 one of the major gains 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

Trade‑offs

  • Higher upfront complexity and 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.

Three Concise Q&A Sections

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

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