Ports & Adapter
Hexagonale Architektur konkret: Ports sind Interfaces, die die Domäne besitzt, Adapter sind Implementierungen am Rand - und die Unterscheidung zwischen treibender und getriebener Seite.
Deutsche Übersetzung in Arbeit
Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.
Auf dieser Seite
"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.