Hexagonal Architecture (Ports & Adapters)
Alistair Cockburn's pattern: the application is a hexagon. The inside contains business logic. The outside communicates through ports (interfaces) with adapters (implementations).
Core Concept
┌─────────────────────────────────────┐
│ ADAPTERS (outer) │
│ ┌─────────────────────────────┐ │
│ │ PORTS (boundary) │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ APPLICATION CORE │ │ │
│ │ │ (business logic) │ │ │
│ │ └─────────────────────┘ │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
The application defines ports. External concerns plug in as adapters. The core never knows which adapter is connected.
Ports
A port is an interface defined by the application core. It describes what the application needs, not how it's fulfilled.
Primary Ports (Driving)
- Define how the outside world drives the application
- Typically: use case interfaces, command handlers, API contracts
- Implemented by the application core itself
- Called by driving adapters (HTTP controllers, CLI, test harness)
Secondary Ports (Driven)
- Define what the application needs from the outside
- Typically: repository interfaces, notification sender, payment gateway
- Defined by the core, implemented by adapters
- Called by the application core
Adapters
Driving Adapters (Primary/Left-side)
- Translate external input into calls on primary ports
- Examples: REST controller, GraphQL resolver, message consumer, CLI parser
- Contain no business logic — transform and delegate
Driven Adapters (Secondary/Right-side)
- Implement secondary ports
- Examples: PostgreSQL repository, Redis cache adapter, SMTP email sender, S3 file store
- Swappable without touching core logic
Testability
The primary benefit: test the core in isolation.
| Test type |
Approach |
| Unit tests (core logic) |
Call primary ports directly, stub secondary ports with in-memory fakes |
| Integration tests |
Use real driven adapters against test infrastructure |
| Acceptance tests |
Drive via primary adapters (HTTP client), real or faked driven adapters |
Because ports are interfaces, fakes/mocks are trivial to create.
Mapping to Clean Architecture
| Hexagonal |
Clean Architecture |
| Application core |
Entities + Use Cases |
| Primary ports |
Use Case input boundaries |
| Secondary ports |
Gateway/Repository interfaces |
| Driving adapters |
Controllers (Interface Adapters) |
| Driven adapters |
Gateways, Repos (Interface Adapters / Frameworks) |
Same dependency rule. Hexagonal emphasises symmetry (all sides equal); Clean Architecture emphasises concentric layers.
Typical Project Structure
cmd/ ← entry points (wires adapters)
internal/
domain/ ← entities, value objects (pure)
application/ ← use cases, port interfaces
ports/
driving.go ← primary port interfaces
driven.go ← secondary port interfaces
adapters/
driving/
http/ ← REST adapter
grpc/ ← gRPC adapter
driven/
postgres/ ← DB adapter
redis/ ← cache adapter
kafka/ ← event publisher adapter
Decision Framework
| Signal |
Recommendation |
| Multiple inbound channels (HTTP + queue + CLI) |
Hexagonal — driving adapters share the same core |
| Need to swap infrastructure (migrate DB, change cloud provider) |
Hexagonal — swap adapters without touching core |
| Heavy testing requirements, CI speed matters |
Hexagonal — in-memory adapters make tests fast |
| Simple single-entry microservice, stable infra |
May be overkill — simpler layering suffices |
| Team unfamiliar with pattern |
Start with simplified version (ports only for driven side) |
Common Mistakes
- Fat adapters — business logic creeps into adapters; keep them as thin translators
- Port explosion — one interface per method; group related operations into cohesive ports
- Shared domain objects in API contracts — never expose domain types directly through driving adapters
- Skipping the port — calling infrastructure directly from use cases defeats the purpose
Cross-References
- See
architecture/knowledge-clean-architecture — same dependency direction, layered vocabulary
- See
architecture/knowledge-ddd — domain layer maps to the hexagonal core
1---2name: knowledge-hexagonal-architecture3description: Hexagonal Architecture (Ports & Adapters): primary/secondary ports, driving/driven adapters, testability benefits, and relationship to Clean Architecture. Decision framework for when hexagonal is the right fit. Use when discussing service boundaries, adapter patterns, or testability.4---56# Hexagonal Architecture (Ports & Adapters)78Alistair Cockburn's pattern: the application is a hexagon. The inside contains business logic. The outside communicates through **ports** (interfaces) with **adapters** (implementations).910## Core Concept1112```13 ┌─────────────────────────────────────┐14 │ ADAPTERS (outer) │15 │ ┌─────────────────────────────┐ │16 │ │ PORTS (boundary) │ │17 │ │ ┌─────────────────────┐ │ │18 │ │ │ APPLICATION CORE │ │ │19 │ │ │ (business logic) │ │ │20 │ │ └─────────────────────┘ │ │21 │ └─────────────────────────────┘ │22 └─────────────────────────────────────┘23```2425The application defines ports. External concerns plug in as adapters. The core never knows which adapter is connected.2627## Ports2829A port is an **interface** defined by the application core. It describes *what* the application needs, not *how* it's fulfilled.3031### Primary Ports (Driving)32- Define how the **outside world drives the application**33- Typically: use case interfaces, command handlers, API contracts34- Implemented by the application core itself35- Called by driving adapters (HTTP controllers, CLI, test harness)3637### Secondary Ports (Driven)38- Define what the **application needs from the outside**39- Typically: repository interfaces, notification sender, payment gateway40- Defined by the core, implemented by adapters41- Called by the application core4243## Adapters4445### Driving Adapters (Primary/Left-side)46- Translate external input into calls on primary ports47- Examples: REST controller, GraphQL resolver, message consumer, CLI parser48- Contain no business logic — transform and delegate4950### Driven Adapters (Secondary/Right-side)51- Implement secondary ports52- Examples: PostgreSQL repository, Redis cache adapter, SMTP email sender, S3 file store53- Swappable without touching core logic5455## Testability5657The primary benefit: **test the core in isolation**.5859| Test type | Approach |60|-----------|----------|61| Unit tests (core logic) | Call primary ports directly, stub secondary ports with in-memory fakes |62| Integration tests | Use real driven adapters against test infrastructure |63| Acceptance tests | Drive via primary adapters (HTTP client), real or faked driven adapters |6465Because ports are interfaces, fakes/mocks are trivial to create.6667## Mapping to Clean Architecture6869| Hexagonal | Clean Architecture |70|-----------|-------------------|71| Application core | Entities + Use Cases |72| Primary ports | Use Case input boundaries |73| Secondary ports | Gateway/Repository interfaces |74| Driving adapters | Controllers (Interface Adapters) |75| Driven adapters | Gateways, Repos (Interface Adapters / Frameworks) |7677Same dependency rule. Hexagonal emphasises symmetry (all sides equal); Clean Architecture emphasises concentric layers.7879## Typical Project Structure8081```82cmd/ ← entry points (wires adapters)83internal/84 domain/ ← entities, value objects (pure)85 application/ ← use cases, port interfaces86 ports/87 driving.go ← primary port interfaces88 driven.go ← secondary port interfaces89 adapters/90 driving/91 http/ ← REST adapter92 grpc/ ← gRPC adapter93 driven/94 postgres/ ← DB adapter95 redis/ ← cache adapter96 kafka/ ← event publisher adapter97```9899## Decision Framework100101| Signal | Recommendation |102|--------|---------------|103| Multiple inbound channels (HTTP + queue + CLI) | Hexagonal — driving adapters share the same core |104| Need to swap infrastructure (migrate DB, change cloud provider) | Hexagonal — swap adapters without touching core |105| Heavy testing requirements, CI speed matters | Hexagonal — in-memory adapters make tests fast |106| Simple single-entry microservice, stable infra | May be overkill — simpler layering suffices |107| Team unfamiliar with pattern | Start with simplified version (ports only for driven side) |108109## Common Mistakes110111- **Fat adapters** — business logic creeps into adapters; keep them as thin translators112- **Port explosion** — one interface per method; group related operations into cohesive ports113- **Shared domain objects in API contracts** — never expose domain types directly through driving adapters114- **Skipping the port** — calling infrastructure directly from use cases defeats the purpose115116## Cross-References117118- See `architecture/knowledge-clean-architecture` — same dependency direction, layered vocabulary119- See `architecture/knowledge-ddd` — domain layer maps to the hexagonal core