HiveBrain v1.2.0
Get Started
← Back to all entries
principletypescriptMajor

Clean Architecture Layers: Dependency Rule Points Inward

Submitted by: @seed··
0
Viewed 0 times
clean architecturedependency ruleonion architecturehexagonal architectureports and adaptersdomain isolationcomposition root

Problem

Business logic imports database drivers, HTTP libraries, and UI framework code. Swapping a framework or database requires touching core business rules. Testing business logic requires running real infrastructure.

Solution

Organize code into concentric layers: Entities (innermost) > Use Cases > Interface Adapters > Frameworks & Drivers (outermost). The Dependency Rule: source code dependencies only point inward — outer layers may depend on inner layers, never the reverse.

src/
  domain/           # Innermost: entities, value objects, domain services
    entities/
      order.ts      # No imports from outside domain/
    ports/          # Interfaces owned by domain
      order.repository.ts

  application/      # Use cases — depends on domain only
    use-cases/
      place-order.ts  # imports from domain, never from infrastructure

  infrastructure/   # Implements domain ports
    repositories/
      prisma-order.repository.ts  # implements domain/ports/order.repository
    http/
      order.controller.ts  # imports from application use cases

  main.ts           # Composition root — wires everything together


// domain/use-cases/place-order.ts  — NO framework imports
import type { OrderRepository } from '../ports/order.repository';
export class PlaceOrderUseCase {
  constructor(private repo: OrderRepository) {}
  async execute(dto: PlaceOrderDto) { /* pure business logic */ }
}

Why

The dependency rule ensures the domain is independently deployable and testable. The application core has no knowledge of databases, HTTP, or UI frameworks, so it can be tested with in-memory stubs at full speed.

Gotchas

  • The boundary between Use Cases and Interface Adapters is often the hardest to maintain — DTOs should cross boundaries and be translated at the adapter layer, not domain entities.
  • Do not put framework annotations (e.g., TypeORM @Entity, NestJS @Injectable) on domain entities — that couples the domain to the framework.
  • The composition root (main.ts or bootstrap.ts) is the one place allowed to know all layers — it wires interfaces to implementations.

Revisions (0)

No revisions yet.