Microservices: Hexagonal Architecture

In short, Clean & Hexagonal architecture: isolate the domain logic from all outside dependencies.
Create your application to work without either a UI or a database so you can run automated regression-tests against the application, work when the database becomes unavailable, and link applications together without any user involvement. Alistair Cockburn

First of all, why do we use architecture? There are many answers to this question, but simply for getting the best flexibility, scalability, and easy maintenance.

Basically, Hexagonal Architecture means isolating the domain logic from outside dependencies such as databases, message queues, or any runtime tool or device.

Hexagonal Architecture is also known as Ports and Adapters pattern. The application has a semantically sound interaction with the adapters on all sides of it without actually knowing the nature of the things on the other side of the adapters.

image.png

For this goal, we use various design patterns and principles, such as Adapter design patterns and Dependency Inversion from SOLID principles.

Port & Adapter

Ports and Adapters, is an architectural pattern that allows input by users or external systems to arrive into the Application at a Port via an Adapter, and allows output to be sent out from the Application through a Port to an Adapter. This creates an abstraction layer that protects the core of an application and isolates it from external — and somehow irrelevant — tools and technologies.

image.png

Port

We can see a Port as a entry point, it determines the interface which will allow foreign actors to communicate with the Application, regardless of who or what will implement said interface. Ports also allow the Application to communicate with external systems or services, such as databases, message brokers, other applications, etc.

Adapter

An Adapter will initiate the interaction with the Application through a Port, using a specific technology, it can be gRPC, REST API or, etc (see above image).There can be as many Adapters for any single Port as needed without this representing a risk to the Ports or the Application itself.

Primary Adapter vs Secondary Adapter

Primary actors are the ones that initiate the interaction, and are always depicted on the left side. Secondary actors are communicate with external services, For example, a database Adapter is called by the Application so that it fetches a certain data set from persistence.

Ports are simply interfaces that the hexagon (application) defines to interact with the outside world. They also allow the outside world to interact with the hexagon.

  • Primary Ports are the API that the application defines to let the outside world interact with it.

  • Secondary Ports are the interfaces the application defines to communicate with the external systems/devices.

Adapters are concrete implementations. They belong outside the core hexagon and are technology-specific implementations for interacting with the hexagon.

Similar to ports, there are two types of adapters: driver adapters and driven adapters.

  • Primary Adapters are components that use a port defined as the application API to interact with the core hexagon.

  • Secondary Adapters are the concrete implementation of the ports defined within the core hexagon, which allows the hexagon to interact with the outside world to complete its tasks.

When should we use this architecture?

The Hexagonal or Ports and Adapters Architecture, is not the silver bullet for all applications. Although the software engineering discipline has made huge advancements, we are yet to experience any development that is a silver bullet.

When designing software systems, we usually focus mainly on the benefits of a particular pattern or technology. However, making a decision based only on the benefits that it provides leads to many problems in the long run.

Rich Hickey once said that programmers know the benefits of everything and the tradeoffs of nothing.

Like every other pattern, Hexagonal Architecture comes with its good and bad parts.

Trade-off

So use any architecture you want, if your team is able to follow it consistently, then you are good. All architectures have some tradeoffs, which is fine, cause Software Design is all about tradeoffs

When properly implemented and paired with other methodologies, like Domain-Driven Design, Ports and Adapters can ensure an application’s long term stability and extensibility, bringing a great deal of value to the system and the enterprise.

  • Need to write adapter mappings. Sometimes it feels like you’re doing the same thing over and over again: database data objects, domain objects, API data objects are almost the same, why separate them? It feels faster to start without.

  • When adding a new feature, you will sometimes need to do work in several places: UI, API, domain, database. In an order processing component, all parts will have knowledge about an Order. If we add for instance the concept of discounts to an Order, this will affect UI, domain logic, database, and APIs. Hexagonal architecture does facilitate you in making these changes in a controlled way, keeping code continuously deliverable.

  • Although it gives guidance, it also leaves a number of decisions open, for instance how to implement an adapter, whether to use data classes or just mapping functions.

  • May require mock objects for testing the interaction with secondary ports.

Benefits

  • Allows us to focus on domain logic and is a good match with a Domain Driven Design approach

  • Guides us in What To Put Where (WTPW) in the code. WTPW is crucial in making and keeping our code habitable. -Getting something to work is half the effort, finding a good place is the other half.

  • Allows faster, more focused automated tests for domain logic, as well as integration with databases and other external services.

  • Guides structuring dependencies, resulting in a clutter-free domain model implementation.

  • Allows independent and incremental evolution of concerns: we can let our APIs evolve at their own pace, the adapter mapping facilitates decoupling.

  • Allows evolution of the domain model to suit business logic better, without having to break APIs or having to migrate a database on every small refactoring.

In future posts we will find out how DDD & clean architecture help us to have better implementation design.

image.png

reosurces: