Hexagonal architecture is a great way to build structure to your system and split it into different layers each of which serves a specific purpose.
Do not let the name trick you into thinking that it contains 6 pieces of logic. It is more of a representation of the multiple sides a hexagon has, which makes it ideal apps that have multiple connections with external systems. Also the hexagon is common to use in UML diagrams.
Now let’s talk about the 3 layers that make Hexagonal architecture.
The way I like to think of adapters is like the I/O of our app. What gives data to the app and then where does the data go?
That might be a HTTP endpoint that invokes our app, or an EventBridge event that our app is listening to. Then on the opposite end, once the app executes its business logic it has to do something with that data.
A very common scenario is to store it in some kind of Database like DynamoDB or MongoDB or even send a notification to the customer. Adapters can be anything that allows our app to communicate with the outside world.
When the data received within the app needs to be processed and execute some business logic, stuff like calculations, data reshaping and other internal to the app processes. This is the domain layer.
Isolating the domain logic is a great practice for building resilient systems that not only can scale easily but make it easy (er!) to get into the depths of what our system is all about.
The ports layer in my opinion is the part that causes the biggest amount of confusion in the whole concept of this architectural pattern. Let’s see if we can make some sense out of it. One of the main selling points of Hexagonal architecture is the fact that it can make our app domain agnostic.
What that means is that our business logic should be decoupled from the specific tools that we use. In other words, our domain should not be aware that our data are going to be stored in DynamoDB. Similarly, the domain should not know that we’re sending it data via an SQS queue. The port layer is the bridge that connects the domain with the adapter.
In typed languages a port is usually an interface that specifies the shape of the data that adapters have to pass to the domain and vice versa.
Let’s take a classic example where our application is a RESTful API which receives data via a POST HTTP endpoint and stores it to MongoDB. The journey would look like that
Our HTTP adapter will process the HTTP request and send the data to the port which will communicate that to the domain. This is where our internal logic will be executed. Stuff like internal calculations, reshaping data etc.
Then we need to follow the same logic in reverse. The domain has the data that wants to store in the DB and has to send them to the repository port which will then send it to the Database adapter.
Port (to domain)
Process data and send data to repository
Port (to adapter)
Connection with MongoDB
You’re probably wondering “yeah that’s cool but why would we go through all that trouble?”
To put Hexagonal architecture into a business perspective, it makes it painless when we want to introduce new features due to the loosely coupled way of structuring our code. We can change parts of our code without causing major disruption.
In addition to that, our future selves will really thank us when it come to debugging an error as we will immediately know where to look.
Did the app return the wrong data? That sounds like an issue in the domain layer. Was there a network issue during that request? Sounds like an adapter issue.
One of my favourite parts of hexagonal architecture is that it makes testing our code much simpler. We have all experienced codebases that are really difficult to test due to their lack of boundaries where all of the implementation is just thrown into a function / method / class / whatever you want to name it that is 100+ lines long.
With Hexagonal architecture each layer is a separate module we can test in isolation. This can be done by mocking its communication with other layer, which gives us the flexibility to have smaller tests that are easier to write and faster to execute. That can then result with higher testing coverage.
The whole concept of “plug and play” adapters is great because that ensures our business logic does not rely the tools we use.
The more specific our business logic is to a specific infrastructure, the more difficult it will to move away from that tool.
How many times have we had to spend days if not weeks trying to find out how to switch from Database A to Database B because our code is too specific to Database A. This tools logic leakage inside our domain is something we need to be careful about.
Hexagonal architecture guides us on how to have clear boundaries between the tools and our business logic. Then once we decide to move away from the tool, it should be as simple as adding a new adapter.
Obviously I am not saying that migrating away from tools is going to be a piece of cake, but how smooth will that transition be within your app, might be the smallest of your concerns.
For those of us working with Serverless apis, it is a known problem that we very have our entire logic inside the handler. Then slowly once our application starts getting bigger and bigger we either end up with gigantic handlers or some weird structure which looks like that the domain logic is tightly coupled with the infrastructure. This is where we need to introduce some boundaries and Hexagonal architecture can help us.
I have to admit that the first time I tried to write some code using this pattern, it felt really weird. I think the biggest issue was not really understanding what kind of problem Hexagonal architecture is trying to solve. With time though it started making a lot more sense, especially when you reach to the point that it is so easy to navigate through the code and find