Files
pulse-memory/skills/uncle-bob/references/clean-architecture.md
T
Pulse ae39e45460 feat: biblioteca inteligente libs/ + 5 novas skills (20 skills total)
NOVAS SKILLS:
- next-best-practices      v0.1.0  (CLEAN) — Next.js App Router, RSC, caching, data
- nextjs-patterns          v1.0.0  (CLEAN) — Next.js 15: Server Actions, route handlers
- vite                     v1.0.0  (CLEAN) — env vars, aliases, proxy, CJS compat
- uncle-bob                v1.0.0  (CLEAN) — Clean Code, SOLID, Clean Architecture
- clean-code-review        v1.0.0  (CLEAN) — naming, guard clauses, anti-patterns, refactoring
- vue                      v1.0.0  (CLEAN) — Vue framework
- vue-composition-api-best-practices v1.0.0 (CLEAN) — composables, Pinia, reactivity

BIBLIOTECA INTELIGENTE libs/ (10 dominios, 11 arquivos):
- typescript/ — TS safe + generics gotchas
- react/ — Next.js App Router + Vite config
- vue/ — Composition API + Pinia
- linux/ — System diagnostic cheatsheet
- database/ — PostgreSQL + MySQL patterns
- browser/ — Chromium CLI + E2E testing
- security/ — SAST audit (OWASP Top 10)
- best-practices/ — Clean Code + SOLID + Clean Architecture
- deploy/ — Docker multi-stack + OpenClaw ops
- + INDEX.md como guia de navegacao

.learnings/ — LRN-20260519-003 criado (biblioteca compartilhada)
2026-05-19 21:03:25 -03:00

7.1 KiB

Clean Architecture — Detailed Guide

The Core Idea

Separate the software into layers. Each layer has a specific role. Dependencies point inward.

┌──────────────────────────────────────────┐
│         Frameworks & Drivers             │  ← DB, Web, UI, devices
│  ┌────────────────────────────────────┐  │
│  │       Interface Adapters           │  │  ← Controllers, Gateways, Presenters
│  │  ┌──────────────────────────────┐  │  │
│  │  │        Use Cases             │  │  │  ← Application business rules
│  │  │  ┌────────────────────────┐  │  │  │
│  │  │  │      Entities          │  │  │  │  ← Enterprise business rules
│  │  │  └────────────────────────┘  │  │  │
│  │  └──────────────────────────────┘  │  │
│  └────────────────────────────────────┘  │
└──────────────────────────────────────────┘

The Dependency Rule

Source code dependencies must only point inward. Nothing in an inner ring can know anything about an outer ring. This includes functions, classes, variables, types, or any named entity.

Layer Details

Entities (Innermost)

  • Encapsulate enterprise-wide business rules.
  • Could be used by many applications in the enterprise.
  • Least likely to change when something external changes.
  • Pure domain objects with business logic. No framework dependencies.
// Pure entity — no imports from outer layers
class Account {
  constructor(
    readonly id: string,
    private balance: number,
  ) {}

  deposit(amount: number) {
    if (amount <= 0) throw new DomainError('Amount must be positive')
    this.balance += amount
  }

  withdraw(amount: number) {
    if (amount > this.balance) throw new InsufficientFundsError()
    this.balance -= amount
  }

  getBalance() { return this.balance }
}

Use Cases

  • Application-specific business rules.
  • Orchestrate the flow of data to and from entities.
  • Direct entities to use their enterprise-wide business rules.
  • Changes to this layer should not affect entities.
  • Changes to external layers (DB, UI) should not affect use cases.
// Use case — depends on entities and port interfaces, nothing else
class TransferFundsUseCase {
  constructor(
    private accountRepo: AccountRepository,  // Port (interface)
    private notifier: TransferNotifier,       // Port (interface)
  ) {}

  async execute(fromId: string, toId: string, amount: number) {
    const from = await this.accountRepo.findById(fromId)
    const to = await this.accountRepo.findById(toId)

    from.withdraw(amount)
    to.deposit(amount)

    await this.accountRepo.save(from)
    await this.accountRepo.save(to)
    await this.notifier.notify(fromId, toId, amount)
  }
}

Interface Adapters

  • Convert data between the format most convenient for use cases/entities and the format most convenient for external things (DB, web).
  • Controllers, presenters, gateways live here.
  • No business logic — only translation.
// Controller (adapter) — converts HTTP to use case input
class TransferController {
  constructor(private useCase: TransferFundsUseCase) {}

  async handle(req: HttpRequest): Promise<HttpResponse> {
    const { fromId, toId, amount } = req.body
    await this.useCase.execute(fromId, toId, amount)
    return { status: 200, body: { success: true } }
  }
}

// Repository implementation (adapter) — converts use case port to DB
class PostgresAccountRepository implements AccountRepository {
  async findById(id: string): Promise<Account> {
    const row = await this.db.query('SELECT * FROM accounts WHERE id = $1', [id])
    return new Account(row.id, row.balance)
  }

  async save(account: Account): Promise<void> {
    await this.db.query('UPDATE accounts SET balance = $1 WHERE id = $2',
      [account.getBalance(), account.id])
  }
}

Frameworks & Drivers (Outermost)

  • Glue code. Minimal.
  • Web framework config, database drivers, HTTP server setup.
  • This is where all the details go. Keep them out of the inner circles.

Ports and Adapters (Hexagonal Architecture)

Clean Architecture is compatible with hexagonal architecture:

  • Ports: interfaces defined by the use case layer (what it needs from the outside).
  • Adapters: implementations in the outer layer that fulfill ports.
// PORT — defined in use case layer
interface AccountRepository {
  findById(id: string): Promise<Account>
  save(account: Account): Promise<void>
}

// ADAPTER — defined in infrastructure layer
class DrizzleAccountRepository implements AccountRepository {
  // Implementation using Drizzle ORM
}

Crossing Boundaries

When data crosses a boundary, it should be in the form most convenient for the inner circle. Never pass database rows or HTTP request objects into use cases.

Use simple DTOs or value objects:

// Input DTO for use case
interface TransferInput {
  fromAccountId: string
  toAccountId: string
  amount: number
}

// Output DTO from use case
interface TransferResult {
  success: boolean
  newBalance: number
}

The Composition Root

All dependency wiring happens at the outermost layer — the "main" or "composition root":

// main.ts — the only place that knows about ALL concrete implementations
const db = new PostgresDatabase(config.dbUrl)
const accountRepo = new PostgresAccountRepository(db)
const notifier = new EmailTransferNotifier(config.smtp)
const transferUseCase = new TransferFundsUseCase(accountRepo, notifier)
const controller = new TransferController(transferUseCase)

app.post('/transfer', (req, res) => controller.handle(req, res))

Testing Benefits

Each layer can be tested independently:

  • Entities: pure unit tests, no mocks needed.
  • Use Cases: mock the ports (repositories, services).
  • Adapters: integration tests against real infrastructure.
  • End-to-end: full stack through the composition root.

Common Mistakes

  • Letting entities import from frameworks (ORM decorators on domain objects).
  • Putting business logic in controllers.
  • Use cases that know about HTTP status codes or database queries.
  • Skipping the adapter layer and having use cases talk directly to the DB.
  • Over-engineering: not every project needs all four layers. Scale the architecture to the complexity.

Pragmatic Application

  • Start with two layers (domain + infrastructure) for small projects.
  • Add use case and adapter layers as complexity grows.
  • The dependency rule is the non-negotiable part. Everything else is negotiable.
  • Frameworks are details. Design your system so switching a framework is possible (even if you never do).