Engineering Playbook
Code Structure

Clean & Hexagonal Arch

Ports and Adapters, The Dependency Rule, and keeping the Core pure.

Clean & Hexagonal Architecture

Standard MVC (Model-View-Controller) works for small apps, but it often leads to "Fat Controllers" tightly coupled to the database.

Clean Architecture (Uncle Bob) and Hexagonal Architecture (Ports & Adapters) are solutions to the same problem: protecting the Business Logic from the Infrastructure.

The Dependency Rule

This is the single most important rule: Source code dependencies must point only inward.

  1. Inner Circle (Entities): Pure business logic. No SQL, no HTTP, no Frameworks.
  2. Middle Circle (Use Cases): Application specific logic (e.g., "Place Order").
  3. Outer Circle (Infrastructure): The Database, the Web Framework (Next.js/Express), External APIs.

The Litmus Test

You should be able to swap your database (Postgres -> Mongo) or your delivery mechanism (REST API -> CLI) without touching a single line of your Business Logic.


Hexagonal (Ports & Adapters)

A practical way to implement the Dependency Rule.

1. The Core (The Hexagon)

Inside here is your Domain. It defines Ports (Interfaces).

  • Example: IPaymentRepository (interface) defines savePayment(). The Core calls this. It doesn't know how it's saved.

2. The Adapters (The Infrastructure)

Outside the Hexagon, we build concrete classes that implement those interfaces.

  • Example: SqlPaymentRepository implements IPaymentRepository.
  • Example: StripeAdapter implements IPaymentProvider.

3. Dependency Injection

At runtime (usually in main or a DI Container), you inject the SqlPaymentRepository into the Core. The Core remains ignorant of SQL, but the code works.

Primary Adapters (Driving)

Things that trigger your app (Controllers, CLI commands, Event Consumers).

Secondary Adapters (Driven)

Things your app controls (Database, Email Service, 3rd Party APIs).


Practical Example: E-Commerce Order Processing

The Core (Business Logic)

The core contains pure domain entities with business rules:

  • Order entity with methods like calculateTotal() and markAsPaid()
  • Port interfaces that define what the core needs (like OrderRepository)
  • Use cases that orchestrate business logic (like ProcessPaymentUseCase)

The core has zero dependencies on infrastructure - no database, no web framework, no external APIs.

The Adapters (Infrastructure)

  • Secondary adapters implement the ports defined by the core:
    • SqlOrderRepository implements OrderRepository for database persistence
    • StripePaymentGateway implements PaymentGateway for payments
  • Primary adapters drive the application:
    • OrderController handles HTTP requests and calls use cases

Dependency Integration

At the composition root, all components are wired together:

  • Database connection created
  • Repository implementation instantiated
  • Payment gateway configured
  • Use cases receive their dependencies
  • Controllers are set up with use cases

When to Use Clean Architecture

  • Domain complexity: Business logic changes frequently
  • Multiple delivery mechanisms: REST API + CLI + Event consumers
  • Long-lived projects: Expect years of maintenance
  • Team scaling: Multiple teams working on different parts

When NOT to use:

  • Simple CRUD apps with minimal business logic
  • Prototypes or MVPs built for validation
  • Projects with single, stable delivery mechanism

Reference