PeakLab
Back to glossary

Clean Architecture

Software architecture promoting independence of business components from frameworks, databases, and user interfaces.

Updated on February 27, 2026

Clean Architecture, conceptualized by Robert C. Martin (Uncle Bob), is an architectural approach that structures applications in concentric layers where dependencies always point inward. Its fundamental principle relies on isolating the business core from technical details, ensuring optimal maintainability and maximum testability. This architecture enables technological evolution without complete system rewrites.

Fundamentals

  • Dependency rule: inner layers never know about outer layers
  • Separation of concerns: each layer has a clearly defined responsibility
  • Dependency inversion: abstractions govern concrete implementations
  • Technology independence: business logic depends on no external framework or tool

Benefits

  • Enhanced testability: business core can be tested without infrastructure or UI
  • Technical scalability: framework or database changes without impacting business logic
  • Superior maintainability: localized modifications through clear boundaries
  • Team independence: parallel development across different layers
  • Code reusability: portable business logic to other projects

Practical Example

Here's a TypeScript implementation illustrating Clean Architecture layers for an order management system:

domain/entities/Order.ts
// Entities Layer (business core)
export class Order {
  constructor(
    public readonly id: string,
    public readonly customerId: string,
    private items: OrderItem[],
    private _status: OrderStatus
  ) {}

  addItem(item: OrderItem): void {
    if (this._status !== OrderStatus.DRAFT) {
      throw new Error('Cannot modify confirmed order');
    }
    this.items.push(item);
  }

  get totalAmount(): number {
    return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  }

  confirm(): void {
    if (this.items.length === 0) {
      throw new Error('Cannot confirm empty order');
    }
    this._status = OrderStatus.CONFIRMED;
  }
}
application/use-cases/ConfirmOrder.ts
// Use Cases Layer
export interface OrderRepository {
  findById(id: string): Promise<Order | null>;
  save(order: Order): Promise<void>;
}

export interface NotificationService {
  sendConfirmation(customerId: string, orderId: string): Promise<void>;
}

export class ConfirmOrderUseCase {
  constructor(
    private orderRepo: OrderRepository,
    private notifications: NotificationService
  ) {}

  async execute(orderId: string): Promise<void> {
    const order = await this.orderRepo.findById(orderId);
    if (!order) throw new Error('Order not found');

    order.confirm();
    await this.orderRepo.save(order);
    await this.notifications.sendConfirmation(order.customerId, orderId);
  }
}
infrastructure/repositories/PostgresOrderRepository.ts
// Infrastructure Layer (adapter)
import { OrderRepository } from '@/application/use-cases/ConfirmOrder';
import { Order } from '@/domain/entities/Order';
import { Pool } from 'pg';

export class PostgresOrderRepository implements OrderRepository {
  constructor(private db: Pool) {}

  async findById(id: string): Promise<Order | null> {
    const result = await this.db.query(
      'SELECT * FROM orders WHERE id = $1',
      [id]
    );
    return result.rows[0] ? this.toDomain(result.rows[0]) : null;
  }

  async save(order: Order): Promise<void> {
    const data = this.toPersistence(order);
    await this.db.query(
      'UPDATE orders SET status = $1, items = $2 WHERE id = $3',
      [data.status, JSON.stringify(data.items), order.id]
    );
  }

  private toDomain(raw: any): Order { /* mapping */ }
  private toPersistence(order: Order): any { /* mapping */ }
}

Implementation

  1. Identify business entities and their invariant management rules
  2. Define use cases representing user intentions
  3. Create interfaces (ports) for external dependencies
  4. Implement adapters (repositories, controllers, gateways)
  5. Configure dependency injection while respecting the dependency rule
  6. Structure directories according to layers (domain, application, infrastructure, presentation)

Professional tip

Always start by modeling your business domain before choosing technical tools. A good test: if you can run your entity and use case unit tests without a database, web framework, or external library, you're on the right track. Clean Architecture excels in complex, long-lived systems where business logic evolves frequently.

  • NestJS with its modular structure suited for layered architectures
  • TypeORM or Prisma as interchangeable persistence adapters
  • InversifyJS for sophisticated dependency injection
  • Jest with separate configurations per layer for testing
  • Nx or Turborepo for managing multi-layer monorepos
  • ArchUnit (Java) or ts-arch for validating architectural rules

Clean Architecture represents a strategic investment for large-scale projects requiring technical agility and longevity. By isolating business logic from implementation details, it enables teams to pivot technologically without questioning accumulated business value. This approach transforms code into a strategic asset rather than technical debt, ensuring optimal long-term ROI.

Themoneyisalreadyonthetable.

In 1 hour, discover exactly how much you're losing and how to recover it.

Web development, automation & AI agency

contact@peaklab.fr
Newsletter

Get our tech and business tips delivered straight to your inbox.

Follow us
Crédit d'Impôt Innovation - PeakLab agréé CII

© PeakLab 2026