Skip to content

Clean Architecture Preset

The Clean Architecture preset enforces Uncle Bob’s Clean Architecture pattern, ensuring all dependencies point inward toward the entities at the center.

What is Clean Architecture?

Clean Architecture is Uncle Bob Martin’s architectural pattern that:

  • Organizes code in concentric circles from entities (center) to frameworks (outer)
  • Enforces the Dependency Rule: dependencies point INWARD only
  • Isolates business rules from external concerns
  • Makes the system testable independent of frameworks, UI, and databases

The core principle: Source code dependencies must point only inward.

When to Use This Preset

Use the Clean Architecture preset when you have:

  • Enterprise applications with long-term maintenance needs
  • Framework independence as a requirement
  • Strict dependency control needs
  • Complex business rules that must be isolated
  • Teams experienced with Clean Architecture principles

Architecture Diagram

%%{init: {'theme':'base', 'themeVariables': {
  'primaryColor':'#b3d9ff',
  'primaryBorderColor':'#1976d2',
  'secondaryColor':'#b3ffcc',
  'secondaryBorderColor':'#2e7d32',
  'tertiaryColor':'#ffffb3',
  'tertiaryBorderColor':'#f57c00'
}}}%%
graph TB
    subgraph Frameworks["πŸ“ Frameworks & Drivers
Layer 3 - Outermost Circle"] Web["Web Framework"] DB["Database"] External["External APIs"] end subgraph InterfaceAdapters["πŸ“ Interface Adapters
Layer 2 - Controllers & Gateways"] Controllers["Controllers"] Presenters["Presenters"] Gateways["Gateways"] end subgraph UseCases["πŸ“ Use Cases
Layer 1 - Application Business Rules"] Interactors["Use Case Interactors"] InputPorts["Input Ports"] OutputPorts["Output Ports"] end subgraph Entities["πŸ“ Entities
Layer 0 - Enterprise Business Rules"] EntitiesCore["Entities"] ValueObjects["Value Objects"] end %% Allowed dependencies (green arrows point inward) Web --> Controllers DB --> Gateways External --> Gateways Controllers --> Interactors Presenters --> Interactors Gateways --> Interactors Interactors --> EntitiesCore Interactors --> ValueObjects style Frameworks fill:#ffccbc,stroke:#e64a19,stroke-width:2px style InterfaceAdapters fill:#fff9c4,stroke:#f57c00,stroke-width:2px style UseCases fill:#b3e5fc,stroke:#1976d2,stroke-width:2px style Entities fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px linkStyle default stroke:#22c55e,stroke-width:2px

Directory Structure

src/
β”œβ”€β”€ entities/ # Layer 0 - Innermost
β”‚ β”œβ”€β”€ user.ts
β”‚ β”œβ”€β”€ order.ts
β”‚ └── value-objects/
β”‚ └── money.ts
β”œβ”€β”€ use-cases/ # Layer 1
β”‚ β”œβ”€β”€ create-user/
β”‚ β”‚ β”œβ”€β”€ create-user.ts # Interactor
β”‚ β”‚ β”œβ”€β”€ input-port.ts
β”‚ β”‚ └── output-port.ts
β”‚ └── get-user/
β”‚ └── get-user.ts
β”œβ”€β”€ interface-adapters/ # Layer 2
β”‚ β”œβ”€β”€ controllers/
β”‚ β”‚ └── user-controller.ts
β”‚ β”œβ”€β”€ presenters/
β”‚ β”‚ └── user-presenter.ts
β”‚ └── gateways/
β”‚ └── user-gateway.ts
└── frameworks-drivers/ # Layer 3 - Outermost
β”œβ”€β”€ web/
β”‚ └── express-server.ts
β”œβ”€β”€ database/
β”‚ └── postgres.ts
└── external/
└── email-client.ts

Boundaries Defined

The preset defines four concentric circles:

1. Entities (Layer 0 - Innermost)

  • Pattern: src/entities/**
  • Tags: entities, core
  • Purpose: Enterprise business rules - entities and domain logic
  • Dependencies: ZERO (pure business logic)

2. Use Cases (Layer 1)

  • Pattern: src/use-cases/**
  • Tags: use-cases, core
  • Purpose: Application business rules - use case interactors
  • Can depend on: Entities only

3. Interface Adapters (Layer 2)

  • Pattern: src/interface-adapters/**
  • Tags: interface-adapters, adapters
  • Purpose: Controllers, gateways, presenters - convert data formats
  • Can depend on: Use Cases, Entities

4. Frameworks & Drivers (Layer 3 - Outermost)

  • Pattern: src/frameworks-drivers/**
  • Tags: frameworks-drivers, infrastructure
  • Purpose: Web, database, external interfaces - framework-specific code
  • Can depend on: Interface Adapters, Use Cases, Entities

Key Rules Enforced

The Dependency Rule

All dependencies point inward. Outer circles depend on inner circles, never the reverse.

src/use-cases/create-order/create-order.ts
// βœ… GOOD - Use Case depending on Entity (inward)
import { Order } from '../../entities/order'
import { Money } from '../../entities/value-objects/money'
export class CreateOrderUseCase {
execute(amount: number): Order {
const price = new Money(amount)
return new Order(crypto.randomUUID(), price)
}
}
src/entities/order.ts
// ❌ BAD - Entity depending on Use Case (outward)
import { CreateOrderUseCase } from '../use-cases/create-order/create-order'
// This violates the Dependency Rule!

Entities Have Zero Dependencies

Entities are pure business logic with no external dependencies.

src/entities/order.ts
// βœ… GOOD - Pure entity
import { Money } from './value-objects/money'
import { OrderItem } from './order-item'
export class Order {
constructor(
public readonly id: string,
private items: OrderItem[] = []
) {}
addItem(item: OrderItem): void {
this.items.push(item)
}
calculateTotal(): Money {
return this.items.reduce(
(total, item) => total.add(item.price),
new Money(0)
)
}
}
src/entities/order.ts
// ❌ BAD - Entity with external dependencies
import { OrderController } from '../interface-adapters/controllers/order-controller'
import axios from 'axios'
// Entities must have ZERO dependencies on outer layers!

Use Cases Define Interfaces for Outer Layers

Use cases define output ports (interfaces) that outer layers implement.

src/use-cases/create-order/output-port.ts
// βœ… GOOD - Use Case defining output port
import { Order } from '../../entities/order'
export interface OrderOutput {
present(order: Order): void
}
// src/use-cases/create-order/create-order.ts
import { Order } from '../../entities/order'
import { OrderGateway } from './order-gateway-interface'
import { OrderOutput } from './output-port'
export class CreateOrderUseCase {
constructor(
private gateway: OrderGateway,
private output: OrderOutput
) {}
async execute(customerId: string, items: any[]): Promise<void> {
const order = new Order(crypto.randomUUID())
// ... add items ...
await this.gateway.save(order)
this.output.present(order)
}
}

Interface Adapters Implement Use Case Interfaces

Controllers call use cases, presenters implement output ports, gateways implement repository interfaces.

src/interface-adapters/presenters/order-presenter.ts
// βœ… GOOD - Presenter implementing output port
import { OrderOutput } from '../../use-cases/create-order/output-port'
import { Order } from '../../entities/order'
export class OrderPresenter implements OrderOutput {
private viewModel: any
present(order: Order): void {
this.viewModel = {
id: order.id,
total: order.calculateTotal().amount,
itemCount: order.items.length
}
}
getViewModel() {
return this.viewModel
}
}

Frameworks Layer Wires Everything Together

The outermost layer contains framework-specific code and composition.

src/frameworks-drivers/web/express-server.ts
// βœ… GOOD - Framework layer wiring dependencies
import express from 'express'
import { CreateOrderUseCase } from '../../use-cases/create-order/create-order'
import { OrderController } from '../../interface-adapters/controllers/order-controller'
import { OrderPresenter } from '../../interface-adapters/presenters/order-presenter'
import { OrderGateway } from '../../interface-adapters/gateways/order-gateway'
import { PostgresDatabase } from '../database/postgres'
const app = express()
const db = new PostgresDatabase()
// Wire dependencies
const gateway = new OrderGateway(db)
const presenter = new OrderPresenter()
const useCase = new CreateOrderUseCase(gateway, presenter)
const controller = new OrderController(useCase, presenter)
app.post('/orders', (req, res) => controller.create(req, res))

Example Configuration

{
"preset": "@stricture/clean"
}

No additional configuration needed.

Real Code Example

Entities Layer

src/entities/order.ts
import { Money } from './value-objects/money'
export class Order {
private items: OrderItem[] = []
constructor(public readonly id: string) {}
addItem(productId: string, quantity: number, price: Money): void {
const item = new OrderItem(productId, quantity, price)
this.items.push(item)
}
calculateTotal(): Money {
return this.items.reduce(
(total, item) => total.add(item.subtotal()),
new Money(0)
)
}
canBeCancelled(): boolean {
// Business rule: Orders can only be cancelled if total < $100
return this.calculateTotal().amount < 100
}
}
export class OrderItem {
constructor(
public readonly productId: string,
public readonly quantity: number,
public readonly price: Money
) {}
subtotal(): Money {
return this.price.multiply(this.quantity)
}
}

Use Cases Layer

src/use-cases/create-order/create-order.ts
import { Order } from '../../entities/order'
import { Money } from '../../entities/value-objects/money'
export interface OrderGateway {
save(order: Order): Promise<void>
}
export interface OrderOutput {
present(order: Order): void
}
export interface CreateOrderInput {
customerId: string
items: Array<{
productId: string
quantity: number
price: number
}>
}
export class CreateOrderUseCase {
constructor(
private gateway: OrderGateway,
private output: OrderOutput
) {}
async execute(input: CreateOrderInput): Promise<void> {
const order = new Order(crypto.randomUUID())
for (const item of input.items) {
order.addItem(
item.productId,
item.quantity,
new Money(item.price)
)
}
await this.gateway.save(order)
this.output.present(order)
}
}

Interface Adapters Layer

src/interface-adapters/controllers/order-controller.ts
import { CreateOrderUseCase } from '../../use-cases/create-order/create-order'
import { OrderPresenter } from '../presenters/order-presenter'
export class OrderController {
constructor(
private createOrder: CreateOrderUseCase,
private presenter: OrderPresenter
) {}
async create(req: any, res: any): Promise<void> {
try {
await this.createOrder.execute({
customerId: req.body.customerId,
items: req.body.items
})
const viewModel = this.presenter.getViewModel()
res.status(201).json(viewModel)
} catch (error) {
res.status(400).json({ error: error.message })
}
}
}
src/interface-adapters/presenters/order-presenter.ts
import { OrderOutput } from '../../use-cases/create-order/create-order'
import { Order } from '../../entities/order'
export class OrderPresenter implements OrderOutput {
private viewModel: any = null
present(order: Order): void {
this.viewModel = {
id: order.id,
total: order.calculateTotal().amount,
canBeCancelled: order.canBeCancelled()
}
}
getViewModel() {
return this.viewModel
}
}
src/interface-adapters/gateways/order-gateway.ts
import { OrderGateway } from '../../use-cases/create-order/create-order'
import { Order } from '../../entities/order'
export class OrderGatewayImpl implements OrderGateway {
constructor(private db: any) {}
async save(order: Order): Promise<void> {
await this.db.query(
'INSERT INTO orders (id, total) VALUES ($1, $2)',
[order.id, order.calculateTotal().amount]
)
}
}

Frameworks & Drivers Layer

src/frameworks-drivers/web/express-server.ts
import express from 'express'
import { Pool } from 'pg'
import { CreateOrderUseCase } from '../../use-cases/create-order/create-order'
import { OrderController } from '../../interface-adapters/controllers/order-controller'
import { OrderPresenter } from '../../interface-adapters/presenters/order-presenter'
import { OrderGatewayImpl } from '../../interface-adapters/gateways/order-gateway'
const app = express()
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
app.use(express.json())
app.post('/orders', async (req, res) => {
// Wire dependencies for this request
const gateway = new OrderGatewayImpl(pool)
const presenter = new OrderPresenter()
const useCase = new CreateOrderUseCase(gateway, presenter)
const controller = new OrderController(useCase, presenter)
await controller.create(req, res)
})
export { app }

Common Violations and Fixes

Violation: Use Case importing Controller

src/use-cases/create-order/create-order.ts
// ❌ BAD
import { OrderController } from '../../interface-adapters/controllers/order-controller'
// Use cases cannot depend on outer layers!

Fix: Define interface in use case, implement in adapter

src/use-cases/create-order/output-port.ts
// βœ… GOOD
export interface OrderOutput {
present(order: Order): void
}
// src/interface-adapters/presenters/order-presenter.ts
export class OrderPresenter implements OrderOutput {
present(order: Order): void {
// Implementation
}
}

Violation: Entity importing Framework

src/entities/order.ts
// ❌ BAD
import express from 'express'
// Entities must have ZERO outward dependencies!

Fix: Keep entities pure

src/entities/order.ts
// βœ… GOOD
export class Order {
// Pure business logic only
calculateTotal(): Money {
return this.items.reduce(
(total, item) => total.add(item.price),
new Money(0)
)
}
}

Benefits

  • Framework independence: Business rules don’t depend on frameworks
  • Testability: Core business logic is easy to test
  • Flexibility: Can swap databases, UI, or frameworks easily
  • Maintainability: Changes to frameworks don’t affect business rules

Trade-offs

  • Complexity: More layers and abstractions
  • Learning curve: Team needs to understand the pattern deeply
  • Indirection: More interfaces to navigate
  • Overhead: Can be overkill for simple applications

Next Steps