Hexagonal Architecture (Ports & Adapters)
What Ports & Adapters is, how it differs from Clean and Onion, and how to apply it to a tool with many inputs and devices. For architects who want a testable core with less ceremony than full Clean Architecture.
- Researched
- 15 Oct 2026
- Reading time
- 6 min
- Sources
- 13
- Review due
- 15 Oct 2029
- Two regions only: an inside (the application) and an outside (everything else), joined by ports the application owns.
- Symmetry: UI, tests, databases and devices are all outside actors; the split that matters is driving (calls in) vs driven (called by the app).
- Tests are first-class actors: the original intent is an app that runs the same under a GUI, a script or a test harness, without its real devices.
- It says nothing about the inside: use layers, DDD or plain code there. That is the main difference from Clean and Onion.
- Worth it with several inputs or volatile outputs (devices, protocols, data sources); overhead for one UI over one database.
On this page 10 sections
The idea in one minute
Hexagonal Architecture, which its author prefers to call Ports & Adapters, splits an application into two regions: an inside that holds the application logic and an outside that holds everything it talks to. The inside exposes and requires technology-neutral interfaces called ports. Technology-specific adapters plug into those ports from the outside.
Alistair Cockburn stated the goal as letting an application be driven equally by users, programs, automated tests or batch scripts, and be developed and tested in isolation from its eventual run-time devices and databases.[1] The interesting asymmetry is not left versus right, UI versus database, as in a layered diagram. It is inside versus outside: “the items outside the application can be dealt with symmetrically.”[1]
When it fits
| Fits when… | Doesn’t fit when… |
|---|---|
| The same logic is driven through several channels (desktop UI, CLI, REST, scripts, tests) | One UI over one database, and that will not change |
| Outputs are volatile or hardware-bound: devices, buses, external services, data sources that move | The app is short-lived or mostly forwards data unchanged |
| You must test business behaviour without hardware or production systems | The team cannot afford the extra adapter code yet |
| The product will outlive several technology choices |
AWS states the trade-off directly: the adapter layer is justified when there are several inputs and outputs or the technology is likely to change; otherwise it is maintenance overhead.[8]
Ports, adapters and actors
| Term | Meaning |
|---|---|
| Port | A purposeful conversation the application owns, named for its goal.[1] The book names them For…ing: ForCalculatingTaxes, ForGettingTaxRates.[4] |
| Driving (primary) port | An interface the application provides; its API or use-case boundary.[5] |
| Driven (secondary) port | An interface the application requires; implemented outside.[5] |
| Driving adapter | Translates an outside actor’s technology into calls on a driving port: GUI, CLI, REST, test harness. It uses the port. |
| Driven adapter | Implements a driven port for one technology: database, message bus, device protocol, simulator. |
| Configurator | The composition root (main) that tells the application which driven adapters to use. The 2024 book calls the idea Configurable Receiver.[3] |
Cockburn usually found two to four ports in an application and noted that choosing a slightly wrong number does little harm.[1] Group operations by what an actor is trying to achieve, not one port per method.[6]
Why a hexagon?
The shape has no meaning; “the hexagon is not a hexagon because the number six is important.”[1] It leaves room to draw several ports and adapters, and it breaks the habit of drawing a one-dimensional stack of layers.
How it differs from Clean and Onion
| Hexagonal | Onion | Clean | |
|---|---|---|---|
| Prescribes | Inside vs outside only | Domain model centre, rings of services and interfaces | Entities, use cases, interface adapters, frameworks |
| Says about the inside | Nothing; any structure is allowed[5] | Domain-model rings[12] | Use-case and entity rings[11] |
| Main split | Driving vs driven actors | Core vs infrastructure | Policy vs detail (the dependency rule) |
| Tests | First-class actors on the driving side | Outside the onion | Outer ring |
Martin presented Clean Architecture as a unification of Hexagonal, Onion and similar ideas,[11] and vendor guides often treat the names as synonyms.[10] Cockburn’s own book contrasts them: Ports & Adapters specifies only two layers, inside and outside.[3] In practice that makes it the lighter-weight member of the family. You get the testable core and swappable edges without committing to a particular internal layering.
Worked example: a configuration tool
An illustrative C# sketch: one driving port, two driven ports, several adapters for each.
// Inside: ports owned by the application
public interface IForConfiguringDevices // driving port (provided)
{
ApplyResult ApplyProfile(DeviceId id, ConfigProfile profile);
}
public interface IForTalkingToDevices // driven port (required)
{
DeviceState ReadState(DeviceId id);
void WriteParameters(DeviceId id, IReadOnlyList<Parameter> parameters);
}
public sealed class DeviceConfigurator(IForTalkingToDevices devices) : IForConfiguringDevices
{
public ApplyResult ApplyProfile(DeviceId id, ConfigProfile profile)
{
var current = devices.ReadState(id);
var changes = profile.DiffAgainst(current); // validate and diff inside
if (!changes.IsValid) return ApplyResult.Rejected(changes.Errors);
devices.WriteParameters(id, changes.Parameters);
return ApplyResult.Applied(changes.Count);
}
}
// Outside: adapters and the configurator
sealed class CliAdapter(IForConfiguringDevices app) { /* parse args, call ApplyProfile */ }
sealed class RestAdapter(IForConfiguringDevices app) { /* POST /devices/{id}/profile */ }
sealed class CanBusAdapter(ICanChannel ch) : IForTalkingToDevices { /* frames, timeouts */ }
sealed class SimulatorAdapter : IForTalkingToDevices { /* in-memory device model */ }
IForTalkingToDevices devices = options.UseSimulator ? new SimulatorAdapter() : new CanBusAdapter(channel);
var app = new DeviceConfigurator(devices); // the configurator decides
Where it is used in practice
- Netflix Studio Workflows. A studio app with entities, repositories (ports), interactors and data sources (adapters). When a data source moved from a JSON API to GraphQL, the team swapped it in about two hours with a one-line change, and data-source choice became configuration. Their suite ran around 3,000 specs in 100 seconds.[7]
- AWS Prescriptive Guidance. Documents the pattern with a Lambda and DynamoDB sample and a longer guide combining it with commands, handlers and optional CQRS.[8][9]
- Microsoft’s .NET architecture guide. Describes the same family under the Clean Architecture name for ASP.NET Core applications.[10]
Gains and costs
What it buys
- Testable without devices or databases. Drive use cases through ports with fakes behind them.
- Many channels, one logic. Desktop UI, CLI, REST and scripts share the same application.
- Swappable technology. A new data source or protocol is a new adapter plus configuration.
- Deferred decisions. Build behaviour against a simulator before the hardware or database is chosen.
- Light prescription. No mandated internal layers, so less ceremony than full Clean Architecture.
What it costs
- Adapter code. Translation in both directions; pure overhead when there is only one input and one output.
- Indirection. One more hop between a request and the logic, and a little latency in hot paths.
- Vague inside. Because the pattern says nothing about the internals, teams still need their own conventions.
- Easy to misread. Drawn as three layers (domain, ports, adapters), it loses its point.
How it goes wrong
- Read as layers. Domain, ports and adapters stacked like N-tier. The pattern has only two regions; ports belong to the inside.[5]
- “Ports are just interfaces.” An interface without a purpose, a name and a test double is not a port.
- One port per method. Fragmented ports that mirror a database API instead of an actor’s goal.[6]
- Technology leaking through ports. ORM entities, HTTP or bus details in port signatures; the swap Netflix relied on becomes impossible.
- Adapters with logic. Validation or rules drifting into the REST or CAN adapter, where tests driving the port never reach them.
- Fakes that lie. The simulator drifts from the real device, and green tests mean nothing.
Testing through ports
| Level | What | How |
|---|---|---|
| Acceptance | Use cases through the driving port | A test adapter is just another driving actor |
| Unit | Logic inside the hexagon | Fakes or in-memory adapters behind driven ports |
| Adapter integration | Each driven adapter against its real technology | Few tests, real bus or database |
| Contract | Fakes still behave like the real thing | Run the same port test suite against SimulatorAdapter and CanBusAdapter[13] |
| System | The wired, deployed tool | A handful of end-to-end checks |
Netflix layered its tests the same way: many interactor tests with mocked repositories, fewer data-source tests checking conformance to the repository interface, and a handful of end-to-end wiring specs.[7]
Origins
- 1994: Cockburn’s earliest hexagonal drawing, reproduced in the 2024 book.[3]
- 2005: the article “Hexagonal Architecture”, already subtitled Ports and Adapters, published as a technical report.[1][2]
- 2008 and 2012: Onion Architecture and Clean Architecture cite it as a shared premise.[12][11]
- 2024: Hexagonal Architecture Explained by Alistair Cockburn and Juan Manuel Garrido de Paz, who died in April 2024, weeks before it went to print.[3]
Key takeaways
- A port is a purposeful conversation owned by the application, named for its goal (for configuring devices), not just an interface.
- Driving adapters use ports; driven adapters implement them. Keep protocol details (HTTP codes, CAN frames) out of port types.
- Every port should come with a test driver or test double; run the same port tests against the simulator and the real adapter.
- A configurator (composition root) chooses which adapters to plug in, which turns technology swaps into configuration.
- Pick it when inputs or outputs vary; for a single-channel CRUD tool the adapter layer is cost without benefit.
Sources
- Hexagonal Architecture (HaT Technical Report 2005.02) · Alistair Cockburn, 2005Primaryalistair.cockburn.us
- Hexagonal Architecture articles · Alistair Cockburn, article list and later notesPrimaryalistaircockburn.com
- Hexagonal Architecture Explained, figures (preview edition) · Cockburn and Garrido de Paz, 2024Primaryalistaircockburn.com
- Hexagonal Architecture Explained, code samples (preview edition) · Port naming and configurator variantsPrimaryalistaircockburn.com
- Ports and Adapters Pattern · Juan Manuel Garrido de Paz, 2018Primaryjmgarridopaz.github.io
- Hexagonal Architecture: Implementation Guide, chapter 1 · Port granularityPrimaryjmgarridopaz.github.io
- Ready for changes with Hexagonal Architecture · Netflix TechBlog, 2020Primarynetflixtechblog.com
- Hexagonal architecture pattern · AWS Prescriptive GuidancePrimarydocs.aws.amazon.com
- Hexagonal architectures: adapting to change · AWS Prescriptive GuidancePrimarydocs.aws.amazon.com
- Common web application architectures · Microsoft LearnPrimarylearn.microsoft.com
- The Clean Architecture · Robert C. Martin, 2012Primaryblog.cleancoder.com
- The Onion Architecture, part 1 · Jeffrey Palermo, 2008Primaryjeffreypalermo.com
- ContractTest · Martin Fowler, 2011Secondarymartinfowler.com
Referenced by
- Which Architecture Style When? Comparison