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.
- Inner Circle (Entities): Pure business logic. No SQL, no HTTP, no Frameworks.
- Middle Circle (Use Cases): Application specific logic (e.g., "Place Order").
- 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) definessavePayment(). 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:
SqlPaymentRepositoryimplementsIPaymentRepository. - Example:
StripeAdapterimplementsIPaymentProvider.
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()andmarkAsPaid() - 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:
SqlOrderRepositoryimplementsOrderRepositoryfor database persistenceStripePaymentGatewayimplementsPaymentGatewayfor payments
- Primary adapters drive the application:
OrderControllerhandles 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
- The Clean Architecture (Robert C. Martin)