Hexagonal Architecture

Ports and Adapters

Hexagonal Architecture is an architectural pattern introduced by Alistair Cockburn and written down on his blog in 2005. The main idea is to structure the application so that it can be run by different clients, and it can be developed and tested in isolation from external tools and technologies.

Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases. – Alistair Cockburn, 2005

Motivation

On the front-end, business logic of the application ends up leaking into the user interface. As a result this logic is hard to test because it’s coupled with the UI. The logic also becomes unusable in other use cases and it’s hard to switch from human-driven use cases to programmatic ones.

On the back-end, business logic ends up being coupled to a database or external libraries and services. This again makes the logic hard to test because of the coupling. It also makes it harder to switch between technologies, or renew our technology stack.

Layered Architecture

Problems with layered architecture…

  • puts database into the center of the application, database-driven design
  • entities leak to the upper layers
  • technical details leak into the business logic, e.g. directly referencing 3rd party APIs
Ports and adapters
Ports and adapters

Ports and Adapters

The main idea is to separate the business logic and the outside world. All the business logic resides inside the application, while any external entities are located outside of the application. The inside of the application should be unaware of the outside.

To make this separation happen, the application only communicates with the outside world through ports. These ports describe the purpose of conversation between the two sides. It is irrelevant for the application what technical details are behind these ports.

The connection to the outside world is provided by adapters. Adapters translate the signals of the outside world to a form understood by the application. The adapters only communicate with the application through the ports.

Separation of business logic and the outside world
Separation of business logic and the outside world

Any port could have multiple adapters behind it. The adapters are interchangable on both sides without having to touch the business logic. This makes it easy to evolve the solution to use new interfaces or technologies.

For example, in a coffee shop application, there could be point of sale UI which handles taking orders for coffee. When the barista submits an order, a REST adapter takes the HTTP POST request and translates it to the form understood by a port. Calling the port triggers the business logic related to placing the order inside the application. The application itself doesn’t know that it is being operated through a REST API.

Connecting external world to the application
Connecting external world to the application

On the other side of the application, the application communicates with a port that allows persisting orders. If we wanted to use a relational database as the persistence solution, a database adapter would implement the connection to the database. The adapter takes the information coming from the port and translates it into SQL for storing the order in the database. The application itself is unaware of how this is implemented or what technologies the implementation uses.

I have seen many articles talking about Hexagonal Architecture mention layers. However, the original article says nothing about layers. There is only the inside and the outside of the application. Also, it says nothing about how the inside is implemented. You could define your own layers, organize components by feature, or apply DDD patterns - it’s all up to you.

Primary and Secondary Adapters

As we have seen, some adapters invoke use cases of the application, while some others react to actions triggered by the application. The adapters that control the application are called primary or driving adapters, usually drawn to the left side of the diagram. The adapters that are controlled by the application are called secondary or driven adapter, usually drawn to the right of the diagram.

Primary and secondary adapters
Primary and secondary adapters

The distinction between primary and seconary is based on who triggers the conversation. This relates to the idea from use cases of primary actors and secondary actors. A primary actor is an actor who performs one of the application’s functions. A secondary actor is someone who the application gets answers from or notifies.

Implementation

When we implement a primary adapter on the driver side, interactions are directed from the adapter to the application through ports. Source code dependencies point inwards, making the application unaware of who is calling it’s use cases.

Implementing primary adapters
Implementing primary adapters

In our coffee shop example, the OrderController is an adapter who calls a use case defined by the PlacingOrders port. Inside the application, CoffeeShop is the class who implements the functionality described by the port.

When we implement a secondary adapter on the driven side, the interactions go out from the application but dependencies still point inwards. To achieve this, we have to apply the dependency inversion principle.

Implementing secondary adapters
Implementing secondary adapters

Now the OrdersJpaAdapter does not use the Orders port but implements it. Also, the CoffeeShop does not implement the Orders port but uses it. This reverses the relationship and allows the application to not know about any persistence details.

Anti-corruption layer for external APIs…

Implementing secondary adapters
Implementing secondary adapters

Not letting database dictate domain design…

Implementing secondary adapters
Implementing secondary adapters

Testing

Unit or acceptance tests for the business logic…

Unit testing the business logic
Unit testing the business logic

Testing the primary adapters in isolation…

Integration testing primary adapters
Integration testing primary adapters

Testing the secondary adapters in isolation…

Integration testing secondary adapters
Integration testing secondary adapters

Covering the entire solution with end-to-end tests…

End-to-end testing the solution
End-to-end testing the solution
Arho Huttunen
Arho Huttunen
Software Crafter

A software professional seeking for simple solutions to complex problems.