TL; DR: hexagonal architecture is a fabulous pattern that has more advantages than the ones for which it has been originally created. One can think in an orthodox vision that patterns do not evolve. That it is important to keep Alistair Cockburn’s pattern like it was described back in the days. One can think that some patterns may evolve, that Hexagonal Architecture has more facets than we think. This article discusses and compares these 2 approaches, takes an example with one new facet related to DDD, before asking an important question to its creator and the dev community at large (at least important for the author of this post ;-)
First Adapters were technological
Hexagonal architecture has been initiated during a project that Alistair Cockburn had done once related to a weather forecast system. His main objective seemed to support a great number of different technologies to receive weather conditions as inputs but also to connect and publish the results of their weather forecast towards to a huge number of external systems.
This explains why he found the concept of interchangeable plugins (now we say ‘configurable dependencies’, following Gerard Meszaros) for technologies at the borders of the app, and a stable application-business code inside (what Alistair called the “hexagon”).
The pattern had some traction by the time (see the amazing GOOS book: http://www.growing-object-oriented-software.com talking about it for instance) but it is after more than a decade after that a community took back this pattern out from its ashes ;-) and made it the new way to be explored deeper. This community was gathering people who wanted to focus and foster more on the business value of our software (rather than pure tech fads): the Domain Driven Design (DDD) community.
Then came the DDD practitioners
As a member of this DDD community, I found the pattern very interesting for many other reasons. But the main one being the capability to protect and isolate my business domain code from the technical stacks.
Why should I protect my domain code from the outside world?!?
I still remember that day in 2008 when I witnessed a bad situation where a major banking app had to be fully rewritten after we have collectively decided to ban a dangerous low latency multicast messaging system (I was working for that bank at the time). We had taken that decision because we were all suffering from serious and regular network outages due to multicast nack storms. We were in a bad and fragile situation where any slow consumer could break the whole network infrastructure shared by many other applications and critical systems of the bank. Sad panda.
Why couldn’t this dev team just switch from one middleware tech to another? Because their whole domain model was melted and built with that middleware data format at its core (i.e. instances of XMessages everywhere in their core domain 😳).
Moreover, the entire threading model of this application was built upon the one from the low latency middleware library. In other words: the need of being thread-safe or not in their code was depending on the premises and the things that were guaranteed so far by the middleware lib. Once you removed that lib, you suddenly lost all these thread-safe guarantees and premises. The whole threading model of this complicated application would have vanished, turned the app unusable with tons of new race conditions, Heinsenbugs and other deadlocks. When it’s an app that can lose Millions of Euros per minute, you don’t play this decision at head or tail ;-)
I’m rarely proponent of rebuilding from scratch and often prefer the refactoring of an existing code that already brings value. But in that case where everything was so entangled... it was really the best thing to do. But I let you imagine how selling this kind of full reconstruction to the business was challenging...
Anyway. I wanted to tell you this story in order to emphasize that splitting your domain code from the technology is a real need. It has real implications and benefits for concrete apps. It’s not a simple architect coquetry. I’m not even talking about the interesting capability to switch from one techno to another in a snap as needed by Alister. No. Just a proper split between our domain code and the infra-tech one.
It’s crucial. But it’s just the beginning of this journey.
Models Adapters
Indeed. With his pattern, Alistair was trying to protect and keep his application domain code stable in front of the challenge of having a huge number of different technologies all around (reason why I talked about Technologist Adapters earlier). But splitting and preserving our domain code from the infrastructure one may be not enough.
As DDD practitioners, we may want to protect our domain code from more than that.
As DDD practitioners we may also want to protect it from being polluted by other models and external perspectives (one can call them other Bounded Contexts).
>>>>> The sub part in grey below explains the basics of DDD you need to understand the end of the post. You can skip it if you already know what are Bounded Contexts (BCs) and Anti-corruption Layers (ACLs).
Bounded Contexts?
A
Bounded Context (BC) is a linguistic and conceptual boundary where words,
people and models that apply are consistent and used to solve specific Domain
problems. Specific problems of business people who speak the same language and
share the same concerns (e.g. the Billing context, the Pre-sales context...)
DDD recommends for every BC to have its own models, taylor-made for its own specific usages. For instance, a ‘Customer’ in a Pre-sales BC will only have information like Age, socio-professional categories, hours of availability and a list of products and services we already bought etc. Whereas a ‘Customer’ in the Accounting-Billing BC will have more specific information such as Address, Payment method etc. DDD advises us to prevent from having one ‘Customer’ model only for every BC in order to avoid conflicts, misinterpretation of words and to allow polysemy between 2 different BCs without incidents.
The interesting thing with the Bounded Context of DDD is that they can be designed autonomously comparing to the other ones (reason why some people are saying that DDD is a way to do Agile architecture).
DDD also comes with lots of strategic patterns in order to deal with BCs relationships. One of them being the famous Anti-Corruption Layer (aka. ACL).
Anti-corruption Layer FTW
As I already explained in another post, the Anti-Corruption Layer is a pattern popularized by the DDD which allows a (Bounded) Context not to find itself polluted by the inconsistency or the frequent changes of a model coming from another Context with which it must work.
>>>>>
An ACL is like a shock absorber between 2 Contexts. We use it to reduce the coupling with another Context in our code. By doing so, we isolate our coupling in a small and well protected box (with limited access): the ACL.
Do you see where I am going with this?
ACL is a kind of Adapter. But an Adapter for Models, not purely technological stuff
As I already wrote elsewhere, an anti-corruption layer can be implemented in various
forms.
At least 3:
- external gateway/service/intermediate API
- dedicated in-middle database (for old systems)
- or just an in-proc adapter within a hexagonal
architecture
And this is the last version that will interest us for the rest of this post.
Indeed, the [technological] adapters of Alistair’s first description of his pattern may be a sweet spot for the [models] adapters we need too when we focus on languages and various models (like when we practice DDD).
And if we do agree on the occasional need of having Models Adapters (aka. ACL) in our Hexagonal Architectures, we can start discussing the options and the design tradeoffs
ACLs in Hexagonal Architecture, OK. But where?
The next question we may ask ourselves is: where to put our ACL code when we want to have it within our Hexagonal Architecture? (I.e. the last choice of the three presented above). There have been debates about it on Twitter recently. And the main question was:
Should we put the ACL outside or inside the hexagon?
As an important disclaimer, I would say that there is no silver bullet nor unique answer to that question. As always with software architecture, the design choices and the tradeoffs we make should be driven by our context, our requirements and set of constraints (either technical, business, economical, sourcing, cultural, socio-technical...).
That being said, let’s compare these 2 options.
Option 1: ACL as part of the Hexagon
Spoiler alert: I’m not a big fan of it. To be honest, been there, done that, suffered a little bit with extra mapping layers (new spots for bugs). So not for me anymore. But since it has recently been discussed on twitter, I think it’s important to present this configuration.
This is the ‘technological’ or the orthodox option if I dare. The one saying that driven Ports and Adapters on the right-side should only expose what is available outside as external dependencies. And to do it without trying to hide the number nor the complexity of what it takes to talk or to orchestrate with all these external elements.
We usually pick that option if we consider that coping with other teams’ structural architecture is part of our application or domain code. Not the technical details of them of course. But their existence (i.e. how many counterparts, APIs, DBs or messaging systems do we need to interact with).
And
for that, we necessarily need a counterpart model in our hexagon FOR EVERY ONE
OF THEM!
Why? Remember, we don’t want our hexagon to be polluted by external technical DTOs or any other serialization formats. So, for every one of them, there will be an adaption (in the proper driven Adapter) in order to map it with its non-technical-version. The one we need for our hexagonal code to deal with it. An Hexagonal-compliant model (represented in blue in my sketch above).
It’s important here to visualize that our ‘Hexagonal code’ in this option, is composed by the Domain code + the ACL one (but an ACL that won’t have to deal with technical formats).
Why I abandoned this option over the years with my projects
To conclude with that first option, I would say that there are 2 main reasons why I abandoned it in lots of contexts:
It forces us to create 1 non-technical-intermediate model (in blue) for every external dependency (leading to 5 different models in our example). This is cumbersome, and bug-prone. I saw lots of devs being tired of all those extra layers for a very limited benefit (i.e. just to follow Hexagonal Architecture by the book)
It opens the door for junior devs or newcomers to introduce technical stuff within our hexagon. “But... I thought it was ok since we already have an ACL in that module/assembly?!?” It reduces the clarity of the domain-infra code duo.
These are the reasons why I progressively moved over the years towards another tradeoff. A new option which is one of my favorite heuristics now.
Option 2: ACL within a driven Adapter
This option consists of putting our ACL code into one or more adapters.
If we think that it makes sense to replace 2 different Adapters into one ACL Adapter doing the orchestration and the adaptation, we can even avoid coding the intermediate layers we previously had for every Adapter (in blue on the option 1 diagram). It means less plumbering code, less mapping and less bugs.
When something changes in one of the external backends used by the ACL Adapter (let’s say a pink square), the impact is even reduced comparing to the Option 1.
Indeed, all you have to change in that situation is your ACL code adapting this external backend concept to one of your domain code's (black circles on the diagrams).
With option 1, you will have more work. You will also have to change the corresponding intermediate data model in blue (with more risk of bugs in that extra mapping).
Clarifications
As I am not an English speaker (one may have noticed ;-P I think it is worth clarifying several points before concluding:
I’m not saying that one should always put ACL in our hexagonal architecture
I’m saying that if you need to have an ACL in your hexagonal architecture, you should definitely use the sweet spot of the Adapters to do so.
I’m saying that in some cases, you can even merge 2 former Hexagonal Adapters into a unique one that will play the ACL role
I always want my ports to be designed and driven by my own Domain needs and expressivity. I don’t want my domain code to use infrastructure or someone else external concepts that should not bother my domain logic. In other words: Putting a driven port for my domain concept, instead of putting a driven port for each external system is not a mistake. It’s an informed decision.
To conclude: hexagonal or not hexagonal?
When Alistair created his pattern, his main driver was to easily switch one technology with another without breaking his core domain code. Adaptability was his main driver and big variance of technologies was his challenge.
I call this the "technological facet" of the pattern, recently confirmed by Alistair on twitter:
“The Ports & Adapters pattern calls explicitly for a seam at a technology-semantic boundary” (https://twitter.com/totheralistair/status/1333088400459632640?s=21)
But one of the keys to the pattern's success in my opinion was Alistair's lack of detail in his original article. We all saw value in it, but almost everyone struggled to understand it. Almost all of us have had our own definition of what a port is and what an adapter is for years (you can open some books if you want to check that out ;-)
This fuzziness allowed some of us to play with it, freely to discover new facets or properties from it.
One may discover that Ports and Adapters were top notch for testing (the "testability facet"). Another one may discover that Ports and Adapters were truly awesome to properly split our domain code from the infrastructure one (the "tactical DDD facet"). Another one may find out that it may help to reduce layering and complexity of our architecture (the "simplicity facet"). Another one may realize that the initial premise of the Pattern was rarely the reason why people were using it. That the ports were often leaky abstractions preventing you to properly cover multiple technologies behind (the devil is in the detail). One may find it interesting for having a good time to market and quick feedbacks about what is at stakes (the "quick feedback facet"). One may find it intersting to postpone architectural decisions at the right moment (the "late architectural decisions facet"). One may find it interesting to Adapt more than technologies. To adapt not only technologies but also external models, like I described here (the "strategic DDD facet").
Even Alistair evolved and changed his mind over the years about important things such as the symmetry or the absence of symmetry of his Pattern (now we all know that the left and right side are asymmetrical ;-)
A multi facets pattern?
I personally think that the beauty of this pattern stands on our various interpretations and implementations.The fuzziness of the original Hexagonal Architecture article from Alistair also has something in common with Eric Evan's Blue Book. It’s so conducive to various interpretations that it ages really well.
Like the image of the Hexagon itself, it’s a multi facets pattern. Maybe richer and more complex than Alistair realized it so far.
My intent here is to ask Alistair and every one of you in the DEV community: should we keep talking about Hexagonal Architecture and its multiple facets, or should we start finding new names for some of those facets and awesome properties?
I’m more than keen to have your answers.
For those that would like to continue to dig a little bit on this topic, here is a list of personal references.
My former articles about Hexagonal Architecture:
- Adapters are true Heralds of DDD - April 2020
- Hexagonal architecture: don't get lost on your right-side - March 2020
- Hexagonal != Layers - April 2016
- A zoom on the hexagonal/clean/onion architecture - August 2013
Some Hexagonal Architecture code I've shared:
Some live coding sessions I've made about it:
- Functional Core, l’alternative FP à l’architecture hexagonale - In french with Bruno BOUCARD - September 2020
- Distill the Core Domain from Your Legacy App - At DDD Europe with Bruno BOUCARD - February 2018
- Alistair in the "hexagone" meetup - In english with Alistair COCKBURN - July 2017
- L’après-midi du DDD 3/3 – Hexagonal Architecture FTW ! - In french with Bruno BOUCARD - July 2017
- Coder sans peur du changement, avec la "même pas mal !" architecture hexagonale - Devoxx 3h University in french with Cyrille MARTRAIRE - April 2015
Isn't Option 2 in slight violation with the Open-Closed Principle? For me Option 1 is not an adversary to Option 2, it can be regarded as the necessary addition to Option 2 in order to protect it against changes/interchanges in the green/pink BCs. It can be two hexagons, one inside of the other, no?
ReplyDelete