Ports and Adapters (Hexagonal)

7 min read

Image
Software architecture, which specifies driving and driven Ports with their Adapters.

Every programmer who took part in a big, fast-pacing project knows, that architecture is a key aspect when it comes to scalability, extensibility, productivity and flexibility of the system. If an architect figured out what part of the system needs special care and attention, then the team could look after the most architectural areas of the program. When everything goes well, and development team knows how to deal with code organization and its mechanics, then we can talk about healthy work environment. Why the environment?

Having a Big Ball of Mud instead of Clean Architecture will eventually drive you and your team crazy. If the team gets crazy, then it has a huge impact on their work. They will quickly experience a burnout, conflicts will start taking place, and people will start quitting the company. This hell of an atmosphere will also spread to the other departments. If the IT permanently cannot deliver any value for the company or for the customers, the result will be lack of trust, what eventually will lead to conflicts between departments. And what brings a lack of cooperation within the firm structure? The nail to its coffin. How one can avoid such a destructive situation? By implementing a proper software architecture in a first place, of course.

Keep It Clean And Funky Fresh

Good job, is a good job, no matter the industry or amount of work need to achieve the business goals. It may be a table made by the carpenter, paint job in your bedroom or car repairing. All these works must be done carefully and with some idea of being professional in mind. If the car, for that instance, will not be carefully repaired, then the driver may loose control over it and even lost his life in the accident. The same responsibility comes, when you develop some life-critical systems, those in the ventilator or surgical robot. Every bug caused by the messy and rigid source code, may lead to life-threatening situation, or even death of the patient.

Ports And Adapters (Hexagonal)

These two names are used interchangeably over the internet and books, but both conveys something meaningful. Personally, I prefer to use the name Ports And Adapters, because in my opinion, it tells more about the essence of this technique. I will continue with the explanation of Hexagonal name in the next paragraphs, now you need to learn what these Ports and Adapters actually are.

On the highest level, what typical software system does? Three things:

If you isolate logic from getting and saving data, then your business rules become agnostic to both input and data manipulation. Thanks to such a trait, you can change data source without touching code from the domain layer. We use term layer, to point out distinction between execution with behaviours and managing of data.

The idea behind this architecture, is to isolate the logic, so it is technical-agnostic. Thanks to such separation, you rely on the abstractions (interfaces or abstract classes) rather than concrete implementations. Let’s see how it all work together.

Only With Domain-Driven Design?

You might have heard, or read, that Ports And Adapters architecture is compatible only with Domain-Driven Design. It is undeniable, that it plays really nice with this approach, but it can be also used in projects, where Domain-Driven Design is not a common practice. But since I advocate for practising DDD, we will focus here on how this architecture fits into this design realm.

Ports And Adapters architecture is implemented on the Aggregate level. As a transactional-safe model, it supports all range of DDD building blocks like Repository, Factory, Entities, and so on. End of digression, back to the Ports.

Ports

There are two types of Port:

These two are exactly what logic needs. It defines what are the expectations towards input data’s form and ways of persisting it. To keep the separation of concerns, domain layer contains only interfaces of each Port. It defines its behaviours as abstract methods, which are implemented inside corresponding Adapters.

Adapters

Every Port must be implemented by at least one Adapter. Adapters lie in their own directory, next to the domain directory. All technical details concerning input and persistence belong to this layer, so be careful and don’t let anything leak from one layer to another. This is a key and fundamental rule of this architectural style.

Architecture Of An Aggregate

Now, let’s take a look at some Python3 code along with the directories containing source files. The most important directory is called domain. It contains all stuff related with the domain, in our case, it reflects building blocks of Tactical Domain-Driven Design.

Allow me to start with the behaviours, ports directory contains interfaces, which will be implemented in adapter layer. Generally, if amount declared Ports is insignificant, and they implement a small amount of behaviours, they can be put in a single Python module named by the Tactical DDD building block it represents. User Repository is a great example.

And this is the Adapter, which is a concrete implementation of the particular Port.

The last, non-obligatory, but ultra convenient layer is one called ui. The Python module named after the driving action keeps all necessary objects like response, request and executing functions all together, so cohesion level remains high. API documentation is generated automatically from classes.

The implementation above comes from the dedicated Microservices Lab. It contains remaining layers and Python code, together with a great documentation placed in the README.md files across the GitHub repository. I’m pretty sure, that this is much more convenient way to browse and navigate the project.

Even More Benefits

Another great aspect of using Ports And Adapters, is ability to write tests easily, especially functional ones. Thanks to separation of domain logic from underlying data, you can easily mock the behaviour of persistence layer and prepare input fixtures. But tested, thus stable software, is not the only benefit of using a proper architecture, there are more.

When you start working on a new Aggregate using ports And Adapters architecture, it is advised to focus on the Domain and business rules first. Keep in mind that logic is the thing, which brings value to the project, not the storage or programming language. Stakeholders do not care about technical details, unless system works as expected, and without disruptions.

This kind of approach also allows you to delay the decisions concerning technologies for the persistent layer. At the beginning of the development process, when business logic is not yet transferred to the source code, you cannot be 100% sure, if given storage service will fit. Maybe the Document-oriented database will not be the best choice, when the data managed by the Domain is strictly relational.

The last significant advantage of this methodology is the almost painless process of upgrading of the libraries you have used in the project. When you go by the book, and keep your Domain library-agnostic, then using the newest versions of external software will stay in the Adapters realm, not interfering the logic. The same goes with the Driving Ports that use the protocols, which may change frequently. Since they don’t mix with the Domain, it is easier to update or replace them.

The Hexagon

Why is it nicknamed a Hexagon? The name comes from the drawing, which is usually used to visually describe the concepts of this architecture. At the heart of the Hexagon, there is Domain with all business logic and DDD building blocks if this approach is used. The two left sides of the polygon represents inbound Ports like web controllers or event listeners. The two right ones symbolize outbound Ports, meaning repositories and their storage.

The Word On Framework

Using a Framework is fine and all, but only when properly applied. When you take a closer look at the Auth service implementation in the Lab, you will notice that there is absolutely no framework usage within domain directory. Flask is used only to receive requests nicely and provide easy routing system. The validation of input is handled by the external library.