Hexagonal Architecture
Hexagonal Architecture
Ports and Adapters Pattern
1. Overview: Hexagonal Architecture, which fully isolates business logic from external technology via ports and adapters
flowchart LR
A["Business logic coupled<br/>to UI/DB/external APIs —<br/>any tech change has system-wide impact"] --"Clarify boundaries<br/>with ports and adapters"--> B["Internal domain core<br/>connects to the outside only via ports"] --"Technology independence,<br/>easy testing"--> C["A system that can be<br/>swapped out in any direction"]
style A fill:#FFEBEE,stroke:#D32F2F,color:#000
style B fill:#E3F2FD,stroke:#1976D2,color:#000
style C fill:#E8F5E9,stroke:#388E3C,color:#000
Definition: An architectural pattern proposed by Alistair Cockburn that places an application’s core business logic (the domain core) at the center of a hexagon, connecting to external systems (UI, DB, external APIs) through ports (interfaces) and adapters (implementations), so business logic never depends on external technology at all.
Characteristics: (Driving Side, left) The external world that drives the application (UI, tests, CLI) — inbound adapters. (Driven Side, right) The external world driven by the application (DB, messaging, external APIs) — outbound adapters. (Dependency inversion) A conceptual precursor to Clean Architecture, fully separating inside from outside via the Dependency Inversion Principle (DIP).
2. Hexagonal Architecture’s core structure
A. The internal business logic (domain core)
flowchart TD
subgraph HEX["Domain Core (inside the hexagon)"]
direction TB
DOM["Domain Model<br/>Entity / Value Object —<br/>core business rules and state"]
SVC["Domain Service<br/>business logic spanning<br/>multiple entities"]
UC["Application Service<br/>orchestrates use cases,<br/>manages transactions"]
DOM --- SVC --- UC
end
subgraph PORT["Ports (interfaces)"]
direction LR
IP["Inbound Port<br/>Use Case Interface —<br/>the entry contract for<br/>the outside to call the core"]
OP["Outbound Port<br/>Repository/Gateway Interface —<br/>the exit contract for<br/>the core to call the outside"]
end
HEX --> PORT
style HEX fill:#1E3A5F,stroke:#1E3A5F,color:#fff
style IP fill:#E3F2FD,stroke:#1976D2,color:#000
style OP fill:#E8F5E9,stroke:#388E3C,color:#000
Domain core components
| Element | Role | Allowed dependencies |
|---|---|---|
| Domain Model | Represents core business concepts, rules, and invariants | None (the purest area) |
| Domain Service | Executes domain logic that doesn’t belong to a single entity | Domain model only |
| Application Service | Orchestrates use-case flow, calls out via ports | Domain model, domain services, ports (interfaces) |
| Inbound Port | Interface contract for the outside to call the core | None (defined by the core) |
| Outbound Port | Interface contract for the core to use external resources | None (defined by the core) |
B. How ports and adapters connect
flowchart LR
subgraph DRIVING["Driving Side (inbound)"]
direction TB
A1["REST Controller<br/>handles HTTP requests"]
A2["CLI Adapter<br/>command-line interface"]
A3["Test Adapter<br/>unit/integration tests"]
end
subgraph CORE["Domain Core"]
direction TB
IP2["Inbound Port<br/>(Use Case Interface)"]
DOM2["Business logic"]
OP2["Outbound Port<br/>(Repository Interface)"]
IP2 --> DOM2 --> OP2
end
subgraph DRIVEN["Driven Side (outbound)"]
direction TB
B1["JPA Adapter<br/>DB persistence"]
B2["Kafka Adapter<br/>publish/subscribe messaging"]
B3["HTTP Client<br/>calls external APIs"]
end
DRIVING -->|"Calls port"| CORE
CORE -->|"Port implementation"| DRIVEN
style DRIVING fill:#E3F2FD,stroke:#1976D2,color:#1E3A5F
style CORE fill:#1E3A5F,stroke:#1E3A5F,color:#fff
style DRIVEN fill:#E8F5E9,stroke:#388E3C,color:#1B5E20
| Category | Port | Adapter |
|---|---|---|
| Definition | Interface contract defined by the core | External technology code implementing the port interface |
| Location | Inside the domain core | Outside the domain core (infrastructure layer) |
| Inbound example | CreateOrderUseCase interface | OrderRestController, OrderCLIAdapter |
| Outbound example | OrderRepository interface | JpaOrderRepositoryAdapter, InMemoryOrderRepository |
| How to swap | Keep the port (interface) unchanged | Swap only the adapter (implementation) — core code untouched |
Layered Architecture vs. Hexagonal Architecture
| Comparison | Layered Architecture | Hexagonal Architecture |
|---|---|---|
| Dependency direction | One-way, top to bottom | All dependencies point inward toward the core |
| Swapping externals | Requires modifying an entire lower layer | Swap only the adapter |
| Testing | Hard to test without a DB or external API | Test the core in isolation with in-memory adapters |
| Complexity | Simple and intuitive | Some upfront cost in designing ports/adapters |
3. Expected benefits and practical application of Hexagonal Architecture
| Category | Key expected benefit | Practical application |
|---|---|---|
| Technology independence | Business logic stays unchanged when swapping DB, UI, or framework | Swap only the JPA adapter when migrating MySQL → MongoDB |
| Testability | Test the domain with in-memory adapters and no external dependencies | Unit-test business logic without a DB using an InMemoryRepository |
| DDD alignment | Bounded Context boundaries align with hexagonal core boundaries | Apply an independent hexagon per MSA service for autonomy |
| Multi-channel support | Connect REST, GraphQL, CLI, and messaging to the same core simultaneously | Serve Web, Mobile, and Batch simultaneously from one business logic core |