Concepts / Building / Hexagonal Architecture
Building

Hexagonal Architecture

This guide explains Hexagonal Architecture, also known as Ports and Adapters: an application with its domain at the center and all technology at the edges. It covers the idea, how it fits event-sourced applications, and what it means for testing and change.

Many applications are built in layers: a user interface on top, business logic in the middle, a database at the bottom. On paper, this separates concerns. In practice, the business logic often ends up depending on the database below it – its tables, its transactions, its quirks – and the domain gets shaped by the technology rather than the other way around.

Hexagonal Architecture, described by Alistair Cockburn, draws the picture differently. There is no top and no bottom. There is an inside – the domain – and an outside – everything the application talks to. The hexagon is just a shape that leaves room for many sides; the number six has no meaning.

Ports and Adapters#

The inside and the outside meet at ports: interfaces that the domain defines in its own terms. A port says what the domain needs or offers, never how it is provided.

  • Driving ports describe what the application can be asked to do – such as executing a command or answering a query. Something on the outside drives them: a user interface, an HTTP API, a test.
  • Driven ports describe what the application needs from the outside – such as reading and writing events, sending an email, or asking for the current time.

Adapters connect the ports to real technology. An HTTP adapter turns requests into commands. A storage adapter turns "write these events" into a call to an event store. The domain knows the ports, but none of the adapters; the dependencies all point inward.

Why It Matters#

Keeping technology at the edges has consequences that reach far beyond the diagram:

  • The domain speaks its own language. Its types and functions are named after the business, not after tables or endpoints.
  • Technology can be replaced. Swapping a web framework, a message broker, or a database means writing a new adapter, not rewriting the rules.
  • Everything can be tested from the inside. The domain can be driven by tests directly, and its driven ports can be served by simple in-memory adapters, so most tests need no infrastructure at all.
  • Decisions can wait. Which database to use, or how to expose the application, does not have to be settled before the domain can be built.

Hexagonal Architecture and Event Sourcing#

Event-sourced applications fit this shape particularly well, because their core is already small and explicit:

  • At the center are the rules: deciders that decide on commands and evolve state from events, and projections that turn events into views. All of them are pure functions or close to it.
  • Driving adapters turn the outside world into commands and queries – an HTTP API, a message consumer, a command-line tool, or a scheduled job.
  • Driven adapters connect to the event store, to read and write events, and to whatever else the application needs, such as a mail server or a payment provider.

In a traditional application, the driven side is where most of the complexity hides, because the database holds the state and the domain has to manage it. In an event-sourced application, the event store only appends and reads facts. The adapter to it is thin, and the domain never needs to know how events are stored – only that they are.

Keep the Core Free of Side Effects#

Hexagonal Architecture says where technology belongs. It does not, by itself, say that the core must be free of side effects – a domain can call a driven port in the middle of a decision, and many do. The combination with a functional core goes a step further: the core does not call anything at all. Everything a decision depends on is read by the shell beforehand and passed in as a value, and everything it causes is returned as events that the shell stores afterwards.

This makes the inside of the hexagon trivially testable – given events, when a command, then events – and leaves the adapters as the only places where anything can go wrong on the way in or out. Those can be tested separately, against the real technology, without any business rules getting in the way.

Where the Boundaries Run#

A hexagon is drawn around one application, but most systems consist of several. Each bounded context can be its own hexagon, with its own domain and its own adapters. The adapters between them carry integration events or calls from one context to another – see Domain Events and Integration Events.

Inside one hexagon, many teams also slice the application by use case rather than by technical layer: everything that belongs to "borrow a book" – the command, its decider, the endpoint, the tests – lives together. This vertical structure and the hexagonal one complement each other: slices divide the inside by feature, the hexagon separates the inside from the outside.

Common Pitfalls#

  • Ports named after technology. A port called saveToPostgres has let the outside in. Name ports after what the domain needs: writeEvents, notifyReader.
  • Adapters with business rules. An HTTP adapter that decides whether a reader may borrow a book has moved a rule to the edge, where it is hard to find and to test. Adapters translate; they do not decide.
  • Too many ports. Not every class needs an interface. Ports are for the boundaries of the application, not for every seam inside it.

Designing the Inside First#

Hexagonal Architecture turns a common habit around: instead of starting with the database schema and building upwards, start with the domain – its commands, events, and rules – and add the adapters it needs. In an event-sourced application, this comes almost naturally, because the events are the domain. The rest is just the way in and the way out.