Ports & Adapters
Hexagonal architecture in concrete terms: ports are interfaces the domain owns, adapters are implementations at the edge - and the driving vs. driven distinction.
On this page
"Hexagonal architecture" is Alistair Cockburn's name, and its more descriptive title is Ports and Adapters. The hexagon is just a picture; the substance is two concepts. A port is an interface the application defines for a way it talks to the outside world. An adapter is a concrete implementation of a port for a specific technology. Learn to see every boundary as a port with adapters plugged into it, and the architecture becomes mechanical.
Ports: the application's sockets
A port is an interface owned by the application core, describing an interaction in the domain's own terms - not the technology's. The core defines the shape; it doesn't care who plugs in.
// A port for something the application NEEDS from outside (a "driven"/outbound port)
interface AccountRepository { // owned by the core, in domain language
Optional<Account> findById(AccountId id);
void save(Account account);
}
// A port for something the application OFFERS to the outside (a "driving"/inbound port)
interface TransferMoney { // a use case the app exposes
TransferResult transfer(AccountId from, AccountId to, Money amount);
}The hexagon's edges are these ports. Everything inside is pure domain and use cases; everything crossing the boundary does so through a port.
Adapters: the plugs
An adapter implements a port for a specific technology. Multiple adapters can satisfy one port, and that's the point - the core is written once, and you plug in whichever adapter you need:
class JpaAccountRepository implements AccountRepository { ... } // adapter: Postgres via JPA
class InMemoryAccountRepository implements AccountRepository { ... } // adapter: a HashMap, for tests
class DynamoAccountRepository implements AccountRepository { ... } // adapter: swap the DB, core untouchedThe core depends on AccountRepository (the port); at runtime, DI wires in JpaAccountRepository for production
or InMemoryAccountRepository for a test. Neither the port nor the domain changes. Adapters are where all the
framework code lives - JPA, Spring MVC, Kafka clients - quarantined at the edge.
Driving vs. driven
The crucial symmetry Cockburn emphasizes: ports come in two kinds, on two sides of the hexagon.
- Driving (primary/inbound) ports - the ways the outside world drives the application. A REST controller, a CLI, a scheduled job, or a test calls in through a driving port (a use-case interface). The adapter here translates an HTTP request into a use-case call.
- Driven (secondary/outbound) ports - the ways the application drives the outside world. The core calls out through a driven port to save data, send email, or publish an event. The adapter translates the domain's call into a database query or an SMTP message.
Driving side (inbound) CORE Driven side (outbound)
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββββββ
β REST adapter βββdrivesβββΊ β Use Cases β ββusesββΊβ JPA adapter β
β CLI adapter βββdrivesβββΊ β + Domain β ββusesββΊβ Email adapter β
β Test βββdrivesβββΊ β β ββusesββΊβ InMemory adapter β
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββββββ
(calls INTO the app) (the app calls OUT)Both sides use ports and adapters, but the dependency still points inward: the driving adapter depends on the use-case port; the core depends on the driven port; the driven adapter depends inward on that port too. The core sits serenely in the middle, depending on nothing but its own interfaces.
A quick test for which side a port is on
Ask 'who initiates the call?' If the outside world calls the application (HTTP request arrives, timer fires), it's a driving port and the adapter sits on the primary side. If the application calls the outside world (needs to persist, notify, publish), it's a driven port on the secondary side. Driving adapters translate external input into use-case calls; driven adapters translate the domain's needs into technology calls.
The application core is a laptop, and its edges have standardized ports - USB-C, HDMI, headphone jack. The port defines the shape of the interaction, not what's on the other end. An adapter is the dongle: a USB-C-to-Ethernet dongle, a USB-C-to-HDMI dongle. You plug in whichever adapter matches today's need, and the laptop is oblivious - it just speaks 'USB-C.' Your keyboard drives the laptop through an input port (driving side); the laptop drives an external monitor through an output port (driven side). Swap the Ethernet dongle for Wi-Fi, the monitor for a projector - the laptop's mainboard never changes. That obliviousness to what's plugged in is exactly the core's relationship to its adapters.
For a hexagonal ledger-legacy, classify each of these as a driving or driven port, and say what the adapter translates: (1) a REST endpoint to post a transaction, (2) sending the customer an email receipt, (3) a nightly scheduled batch that reconciles accounts, (4) reading transactions from the Postgres database.
In hexagonal architecture, what is the difference between a port and an adapter?
Key takeaways
- Hexagonal architecture = Ports and Adapters: a port is a core-owned interface; an adapter is a technology-specific implementation at the edge.
- Ports are defined in the domain's language and owned by the core; adapters (JPA, SMTP, Kafka, in-memory) quarantine all framework code at the boundary.
- Multiple adapters can satisfy one port - swap production for in-memory in tests without touching the core.
- Driving (inbound/primary) ports are how the outside world calls into the app (REST, CLI, scheduler); driven (outbound/secondary) ports are how the app calls out (DB, email, events).
- Both sides use ports and adapters, but dependencies always point inward: adapters depend on ports, the core depends only on its own interfaces.