Hexagonal Architecture: The Good And The Hard Parts
Thereās a lot of stuff written about Hexagonal Architecture. I also wrote some things about it, but covered just the basics.
Now with more experience of using the Hexagonal Architecture style in Java with Spring Boot, I thought itās time to reflect what are the good and the hard parts of it.
Few Words For The Beginning
You may notice that I donāt write āThe Good And The Bad Partsā. Thatās because on one side I really like the application of Hexagonal Architecture in the context of professional work. On the other side I found that the āhard partsā are a challenge in nearly every architecture style and not just in Hexagonal Architecture.
So, Iām hesitating to call it the ābad partsā.
Furthermore, many of the things mentioned here apply also to other styles like the Clean and Onion Architecture.
The Good Parts
Hereās what I found good about it:
The principles can be reused
Hexagonal Architecture relies on the principle of separating the business logic from the infrastructure and communicating via interfaces. This separation of concerns and isolating components from each other with well-defined boundaries is a principal that is helpful in many situations. With time, these principles become second nature and are applied to other projects nearly automatically.
Furthermore developers can work isolated from each other. They can work on the business logic without being affected by the infrastructure. This is especially helpful when working in a team or in a large codebase.
Keeping important parts decoupled is easier
If applied correctly, then the business logic is isolated from the outside world. It can be isolated by other logic around it like authentication, infrastructure etc. Itās also possible to go even farther and decouple the framework from the business logic. This can be helpful when the choices of a framework are not the best for the business logic or should be done in a later stage of development.
It makes things easier when every service follows this
When every service follows this style itās easier to navigate through the code. Itās easy to find the parts which are important. Furthermore, patterns in the code can be repeated and reused. It opens up the possibility to create libraries which can be used in other services.
Services can evolve better
Due to the nature of decoupled components itās easier to change parts of the code without affecting other parts. It prevents this ābig ball of mudā which is hard to change.
It also enables a way to create a Modular Monolith instead of Microservices like it was evangelized in the past years: Isolate the modules from each other and let them talk to each other via interfaces. When splitting it out to microservice later on, the business logic and interface can be kept. Just the inbound/outbound connections need to be changed when moving to microservices. This can also be done in small steps.
Interfaces make it also possible to have multiple implementations for different use cases. In my experience thatās helpful when having local classes and production classes.
Testability is easy
In my experience itās easy to test a service which follows the Hexagonal Architecture. The business logic can be tested without the need of a database or other infrastructure. This is especially helpful when writing unit tests.
Mocks can be created easily and side-effects can be prevented when the isolation of components is well-done. This is especially helpful when writing integration tests.
The Hard Parts
There are a few things to keep in mind when working with the Hexagonal Architecture and are (still) hard. Like:
Developers need to change their mindset
The learning curve for the Hexagonal Architecture is not overwhelming steep. But it needs a different mindset when coming from projects with a layered architecture or āemerging architectureā.
One thing which helps to understand it better, is knowing the Dependency Inversion Principle. Dependency Injection is also a big part of understanding it easier.
Thereās no free directory structure
When starting with the usage of the Hexagonal Architecture for the first time, discussions come around the directory structure of the project.
It doesnāt provide this structure out of the box!
The top layers like api/inbound, domain/business and infra/outbound are the obvious ones. But what about the deeper layers? Packaging by feature within the top layers can be helpful to maintain a clear structure for the different functionalities.
This is a good starter, but few questions can come up during further development like: How does the structure reflect what the Ports & Adapters are?
And also some language-specific things like in Java: Which access modifiers should be used? Should Java modules be used to control the access and the dependencies?
Furthermore naming challenges can happen, like that someday a Provider of a Provider needs to be created. Itās easy to take the separation and abstraction way too far.
Knowing these pitfalls can help to maintain a clean codebase which is well-structured and prevents circular dependencies.
Donāt neglect the outer layers
Itās easy to think that the domain layer is the most important one to neglect the outer layers, but: Theyāre important! Itās the place where the important data comes in or is accessed/stored.
In the outer layers is the authorization and security of the service. They should be made good, too. Especially layers which point to the outside and are forming the contract to other users like the API controllers and DTOs.
Death by 1000 interfaces
Even when itās the good part of the Hexagonal Architecture that the communication happens through interfaces, it can also be the downside.
Code navigation in IDEs can be cumbersome, because clicking on a method always navigates to the interface first, before the actual implementation can be accessed. Thatās something developers need to get used to.
Itās always part of discussions and questionable if the interfaces are really needed and whether it would be better to have clear boundaries and let the classes communicate directly. The idea is to an interface only when a second implementation of it is needed.
But this would mean that a lot of effort is needed to enforce it. Some tooling with fitness functions or import control with e.g. checkstyle can help.
Interfaces on the other side āscreamā the boundaries to the outside.
Another challenge is the mapping between all the DTOs, models and DAOs. Many of this can be done by generators (like mapstruct).
If done without any excuses, then a class needs to be created separately for the Model, DTO and DAO. Even if theyāre looking all the same.
Itās still not a silver bullet
Even when it has all these upsides, the Hexagonal Architecture should not be thrown at all problems.
Itās good for large services which are intended to evolve and stay for a while. But for some things like developing libraries, SDKs, small services, job services etc. there are probably better architectures.
Conclusion
This post showed the benefits of Hexagonal Architecture and how services and developers can benefit from it.
I can just encourage to try it, when you havenāt yet. Maybe youāll find that itās a too big āoverkillā for your use case. However, then still few points of the Hexagonal Architecture, like keeping different layers strictly isolated from each other, apply and can be helpful to have a better maintainability.