Hexagonal Architecture = Ports & Adaptors
Any discussion of Microservices would be incomplete without a brief detour through the world of Hexagonal Architecture. In 2005 Alistair Cockburn presented his Ports & Adapters approach in 2005 as a solution for dealing with the problems encountered with traditional n-tier applications, coupling, and code entanglement. The goal was to provide isolation between the domain and its consumers and to improve the overall maintainability of the domain code. We introduce it here as it aligns well with the goals of microservices and we will be referring to it in future posts.

With the Hexagonal Architecture approach, we isolate the core application logic from its interactions with the outside world. This structure enables support for multiple client types (e.g., web browser, mobile apps, etc.) while also providing an abstraction between the core application logic and its externally dependent services. This abstraction prevents client and external services specifics from leaking into the domain.

Alistair Cockburn selected the Hexagon shape as a graphical way to depict the application, hence the name Hexagonal architecture.

Outside the Hexagon

We divide elements external to the hexagon into two groups: Primary Actors and Secondary Actors. A primary actor appears on the top/left of the hexagon. These actors are users of the system.

Secondary actors appear on the bottom/right of the hexagon and represent actors that are acted upon by application. Each secondary actors provide some service to the application as either a Repository or as a Recipient. Repositories provide an abstraction for the applications persistence mechanism and Recipients represents a service that the application sends notifications to (e.g., a Message Queue or email server). the Hexagon Pattern

Inside The Hexagon

Inside the hexagon, we define the application's core business logic. Historically the pattern doesn't dictate the contents of the hexagon, but by convention, it is often illustrated as three concentric rings of hexagons: Framework, Application, and Domain with the Domain in the center. The domain logic at the core of the hexagon is defined without regard to a particular technology or framework. By maintaining business logic purity, we avoid coupling to a specific framework. The isolation this approach affords provides a high degree of future-proofing to the core logic by preventing any framework-specific mechanisms from influencing the core logic.

Surrounding the domain logic is a set of ports which mediate all access to the domain model. These ports provide an abstraction layer around the domain and define the application's API.

Surrounding The port/application layer is a set of adapters which mediate access to the application's ports from the outside world including any supporting framework. These adapters provide an abstraction layer between Primary actors who act upon the business logic and the application ports acting upon the Secondary actors.

Ports

When the outside world wants to communicate with the business domain, it must pass through a Port. Each port provides the application's API for a given group of functions. Ports form the interfaces that the application exposes to the outside world. It should be impossible for any external actor to communicate into the application via any other channel. This approach is intended to support the Information Hiding Principle. Each group of operations exposed by the application is given a port usually named for the function it provides (e.g., update user profile, delete user account, etc.). Each port exposes the application domain API in a technology-agnostic fashion. By decoupling the port from any specific communication modality, the port is available to any mechanism capable of invoking it.

Driver Ports

Driver ports are the boundary between the domain and its external clients. These ports provide a logical separation between the domain and the technology used to communicate through (e.g., HTTP/REST, Message Bus, etc.). Driver ports are intended to avoid coupling the domain to any specific technology. While there is no explicit rule, it is a common practice to implement the port using a Command Bus. The Command Bus is an implementation of the Message Bus Pattern on which Command Messages are transmitted.

Driven Ports

On the outbound side of the domain are the Driven ports which allow the domain to interface its outbound traffic to the world. These ports form the Service Provider Interface (SPI) of the service that the domain communicates through to the outside world. Driven ports are also intended to decouple the domain from any specific technology.

Adapters

Adapters provide the mechanism through which the outside world communicates with the hexagon's Ports. Unlike Ports, adapters are technology-specific. Each adapter is responsible for bridging between the abstracted port API and the real world performing any necessary translations.

Driver Adapters

On the Driver Adapter side, each adapter must translate the client-side's technology-specific request into the appropriate technology-agnostic command. Common driver adapter examples include HTTP/REST adapters that extracts data from the technology-specific HTTP request or a Message Bus adapter client.

Driven Adapters

On the Driven Adapter side, an outbound Adapter provides a concrete implementation of the Driven port's outbound Service Provider Interface (SPI). In this case, the technology-agnostic outbound message is converted by the SPI into a form can be used by the SPI's transport mechanism to communicate to the outside world.

Inside the Domain

By abstracting the domain from its ports and allowing the adapters to mediate the technology & transport specifics we are left with a pure domain. This pure domain contains the business logic distilled into its essence without the baggage of client-specific or external service-specific details. The isolation this provides allows us to to do several things.

First, the internals of the business logic can change without impacting the application clients. Secondly, the business logic is directly testable. Directly testing the domain simplifies the validation process by eliminating the need to test through any supporting framework. Lastly, we are now able to add support for new application client types by simply creating a new client-specific adapter. If we wish to support a new outbound destination we can implement a new SPI for that destination.

Summary

We have seen that the Hexagonal Architectural approach provides another technique to increase isolation. First, isolating the domain from the application API, and then isolating the application API from the mechanisms that invoke it and that it invokes. By employing these isolation techniques, the Hexagonal Architecture illustrates a simple approach to decouple an application's domain logic from the outside world.

Coming up

Now that we have covered the basics of Hexagonal Architecture we are ready to discuss a specific variation of the microservices approach that we will be following going forward. In the next post, we introduce Reactive Microservices.