Did you ever face problems when comes the time to upgrade the stack of your software? Are you able to distinguish your functional tests from your integration ones? Migrating your legacy means rewriting everything from scratch?
Discover how the Hexagonal Architecture, a clean architecture pattern also known as Ports and Adapters, can help!
In a previous experience, my team had to port an old application on a brand-new stack. The software was moving from an EAR/SQL app to a self-contained JAR using NoSQL. By studying it, we quickly realized that we had to redo the entire infrastructure. In fact, the only thing that didn’t have to change was the business logic. So, it makes sense to reuse it, right?
After a deeper look, the maven module named model was POJOs with only getters and setters, totally anemic… Although there is also service module, the business logic is shared across all the layers. It was drowned in a lot of technical code such as DAOs creation, Serialization, etc. There were no ways to extract the business logic! Some parts of it were relying on the technical behavior of the old framework we tried to remove. And why? Because there was no clean separation between the business logic and the technical code.
The Hexagonal Architecture created by Alistair Cockburn ensures the re-usability of the business logic by making it technical-agnostic. So, changing the stack will have no impact on the domain code.
One key concept of this architecture is to put all the business logic into a single place named the Domain. Let’s update the previous schema:
Important constraint: the domain depends on nothing but itself; this is the only way to ensure that the business logic is decoupled from the technical layers. How do we achieve that on the previous schema? The domain clearly depends on the persistence layer! Well, by using a pattern you may know: the inversion of control. By calling the domain the Hexagon, it will look like this:
The inversion of control was pretty magic, let’s see later how it works. Now you got an overview of what is the Hexagonal Architecture:
- Only two worlds, inside and outside the Hexagon. Inside: all the business logic, Outside: the infrastructure – meaning all your technical code.
- The dependencies always go from outside toward the inside of the Hexagon. It ensures the isolation of the business domain from the technical part.
- One corollary of this is the Hexagon depends on nothing but itself. And not only regarding your own layers: It must not depend on any technical framework. That includes external annotations like Jackson or JPA.
Let me explain you a bit more the last point, which is a really important one. In another experience, my team had to port an application from the “classic” Spring framework to Spring Boot. The main (painful) problem we had: leveraging too much on the Spring Integration Tests to validate our functionalities. Furthermore, those functionalities were also too coupled with Spring.
At the first try of our Spring Boot migration, all functional tests were failing. We were not able to determine if the business logic was broken somewhere or if the reason was purely technical. We eventually figured out that it was an integration problem at test level. So we fixed all the tests one by one by crossing our fingers… Hoping the domain was still correct.
No framework used in the Hexagon means the business domain can be reused regardless the change of the technical stack. It will as well increase the testability of your domain since you no longer mix it with integration issues. And at the end, you’ll do real functional tests thanks to this constraint of the Hexagonal Architecture. This way the functional tests will directly interact with the Hexagon and only with it.
NOTE: In Maven, you can ensure this constraint using the enforcer plugin.
Remember the inversion of control? To ensure the isolation of the Hexagon, the dependencies on downstream layers have been inverted. The trick is in fact pretty simple as you can see:
The outside of the Hexagon (the infrastructure) is divided in two virtual parts, the left side and the right side. On the left, you got everything that will query the domain (the controller, the REST layer, etc.). And on the right, everything that will provide some information/services to the domain (persistence layer, third party services, etc.).
Protecting the domain with an anti-corruption layer
To let the outside to interact with the domain, the Hexagon provides business interfaces divided in two categories:
- The API gathers all the interfaces for everything that needs to query the domain. Those interfaces are implemented by the Hexagon.
- The SPI (Service Provider Interface) gathers all the interfaces required by the domain to retrieve information from third parties. Those interfaces are defined in the Hexagon and implemented by the right side of the infrastructure. We will see that on some circumstances, the Hexagon can also implement the SPI.
There are two important facts here:
- The API and the SPI are parts of the Hexagon.
- The API and the SPI only manipulate domain objects of the Hexagon. It indeed ensures the isolation.
In a layered architecture, the Business Object or the service usually creates the DAOs. In the Hexagonal Architecture, the domain only handles domain objects. Consequently, the persistence is in charge to translate the domain objects into any “DAOs” to be persisted. This is what we call an adaptation.
The modularity strength of the Hexagonal Architecture
As mentioned above, the Ports and Adapters architecture is another name of the Hexagonal Architecture. It comes from the power of the modularity of this architecture. Because everything is decoupled, you can have a REST and JMS layers in front of your domain at the same time without having any impacts on it.
On the SPI side, you can change from a MongoDB driver implementation to Cassandra if needed. Since the SPI won’t change because you change the persistence module, the rest of your software won’t be impacted. The API and the SPI are the Ports and the infrastructure modules using or implementing them are the adapters.
One more rule here: always start with the inside of the Hexagon. This will bring you a lot of advantages:
- Focus on the feature instead of the technical details. Because only the feature brings value to your company. A developer working on another business domain is able to put in place a Spring Controller. But the double declining balance method will sound like Wookiee for him unless he worked for an accountancy company.
- Delay choices on technical implementation. Sometimes it’s really hard to know which technical implementation you really need at the beginning. Consequently, delaying this choice helps you to focus on what brings the primary values to your company – the feature. Furthermore, after the implementation of the business logic, some new elements can help you to make the best choice regarding your infrastructure. You can discover that the domain is more relational than expected, so SQL is a good choice for your database.
- One corollary is it ensures the Hexagon is a stand-alone. Since you should never write code without tests, it means that the Hexagon is self-tested. Furthermore, we got here real functional tests focusing on the business only.
A detailed implementation of the Hexagonal Architecture is covered in the following article
Digging a Hexagonal Architecture and Domain-Driven Design example built with Spring Boot & Kotlin with code samples and a git repository.
A Test-Driven implementation
With the Hexagonal Architecture, you put your functional tests in your domain. Those tests will call directly the domain API while avoiding any disturbance from the technical part. In a way you are creating an adapter simulating the Controller to test the features of the domain.
Starting with the functional tests of the Domain
My advice is writing your functional scenario first using the Behavior-Driven Development to describe your feature.
Check out those articles to learn more about Behavior-Driven Development (BDD) and How to implement a Functional Test.
Behavior-Driven Development (BDD) is a really powerful tool which help us to build value-based robust software. Let’s make a point about what BDD really is, figuring out the anti-patterns and the best practices.
A practical guide and best practices to write functional tests with Cucumber for an application developed with Hexagonal Architecture and Domain-Driven Design
The first step of the “double-loop” will produce your functional test using ATDD. And write the API interface which will be the entry point of your feature. Then implement your tests with TDD and finally implement your business logic. While writing it, you might need to retrieve some data from the database for example, so create an SPI. Since the right side is not yet implemented, create a stubbed implementation of this SPI inside your Hexagon. That can be achieved using an in-memory database implemented with a Map.
You can choose to keep the Stubs in the test scope of your application. But you can as well temporarily ship it if needed. For example, once we made the first feature on the Hexagon, we stubbed an external service and the database. Because our client needed us to provide an interface contract, we secondly exported the domain through a REST controller. So we shipped a first version with stubbed data on the right side of the infrastructure. This way, the client was able to see the structure of our data and the expected behavior of the feature. It was much more reliable than creating by hand some JSON samples of our requests and responses because it actually deals with real business constraints.
Finishing with the adapters
The next step is usually opening the left side first. This way you can put in place some integration tests on the feature. At this time you can provide some live documentation and ensure an interface contract with your clients.
Finally, open on the right by implementing the SPI of your feature by taking advantage of the integration tests. I strongly recommend your tests to be stand-alone to avoid any instability during build time. You should always mock your third parties using something like Wiremock for external services or Fongo to simulate a MongoDB.
Loop the same way for your other features.
For more information, an Hexagonal Architecture Testing Strategy is available on GitLab which is also described in the talk above 🇫🇷.
So, now we have seen what is Hexagonal Architecture! There is a real benefit in decoupling the business logic from the technical code. It ensures your business domain is durable and robust regarding the continuous evolution of the technology.
The Hexagonal Architecture offers you a real means to achieve this by:
- Putting all the business logic in a single place.
- The domain is isolated and agnostic regarding the technical part because it depends on nothing but itself. That’s why the dependencies always go from outside to the inside of the Hexagon.
- The Hexagon is a stand-alone module. Thus, It increases the testability of your domain by writing real functional tests which don’t have to deal with technical issues.
- This architecture offers a powerful modularity. It helps you to write as many adapters as needed with a low impact on the rest of the software. And since the domain is agnostic from the stack, the stack can be changed without any impact on the business.
- By always starting with the domain, you ensure to bring value to your customer by focusing on the feature development. This way you can delay choices on technical implementation to make the best choice at the right time.
Hexagonal Architecture is not suitable for all situations. Like Domain-Driven Design, this is really applicable if you got a real business domain. For an application which transform a data to another format, that’s might be overkill.
To finish on this, always be pragmatic when you adopt a new technology. As stated before, the Hexagon must not depend on any technical framework, but exceptionally you can. For example, in our case the Hexagon had three exceptions: Apache Commons Lang3 (StringUtils), SLF4J and the JSR305 of Findbugs. Because we didn’t want to create the wheel since those frameworks had very low impacts on the domain. One good side effect of the Hexagonal Architecture, is that you keep challenging yourself before integrating a new framework. By using this architecture, we reduced the number of dependencies from fifty to only three or four for the domain. And this is very good from a security perspective.