Featured image of post Theory

Theory

Fasten your belt, and see how one move from blob to hexagonal architecture.

A well-known anti-pattern is the blob one i.e., a workspace - or more accurately a space - where everything is mixed up together, ending up with tight coupling, blurred boundaries, high maintenance cost, low ability to evolve, and more…

  • No structure. No discipline.
  • No relationship pattern e.g., random dependencies.
  • N-flavor projects… Like candies, we cannot be sure of what we get & it does not necessarily taste good.
  • Unpredictable behavior i.e., what happen if… or could I add/remove/swap/…

To tackle this, most of the architecture patterns introduce layers. Layer is a common way to:

  • Define scope by gathering stuff that shares same semantic/trait/… together.
  • Define boundary and act as bulkhead partitioning, guarding one bunch to be noised/modified/broken by other ones.
  • Define coupling and communication rules between those bunches

Hexagonal architecture is one of those particular layered patterns that gains some exposure in the past 2 decades and that deserves attention as it structures the way we think about crafting modern software.

History

One of the most known layered pattern is the multi-layer one.

We can see it as a great improvement compared to previous blob, and for sure it is, but it comes with some limitations too, due to its inherent top-bottom or more accurately said one-way dependencies flow.

In fact, it does half the job by structuring one side of our application often known as Presentation.

Multi-layer 3-tier

But even if things seem to be better now, some unwanted coupling remains namely the one between Business Logic and Data Access. Or maybe it makes sense to have some relationship here, but not this way. Main shortcut of this approach is that it emphasizes spatial split in a way our brain ends up with:

  • Considering one of the two endpoints as the most important one
  • Considering both endpoints as the most important ones

Obviously, it does not make sense because everyone noticed that there is but one layer that comes with business concern, in between the two endpoints… And at the very end, what we do, is crafting software to cope with business concerns. To overcome this limitation, new patterns have emerged for more or less two decades now.

Taxonomy

Ports and Adapters Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases.

Alistair Cockburn, 2005

Onion Architecture ​​​​​​​The application is built around an independent object model where inner layers define interfaces and Outer layers implement interfaces. Direction of coupling is toward the center allowing all application core code to be compiled and run separate from the infrastructure.

Jeffrey Palermo, 2008

So, see how one could depict this leveraging layers.

Layers

When we talk about layers, regardless of the pattern, we relatively quickly hit the point of making a decision regarding cardinality. It is pretty muck like lasagne where most of the chefs have opinion about the ideal number of layers one should stack to cook the best dish… There is no universal answer - and for sure it is not 42 - and YMMV but a good guideline could be:

  1. Do take enough layers to achieve goal
  2. Don’t take too much layers to avoid endless discussion about neighborhood dispute…

So, get back to our layers. A common way is to split into 6 layers of abstraction namely Domain Model, Application, Infrastructure, Presentation, Foundation & Cross-cutting.

One can find other names for specific layers - but the scope and the intent remain the same - e.g.,:

  • Presentation aka Primary or Driving
  • Infrastructure aka Secondary or Driven

Those layers can be schematically mapped as follow:

Main four layers result from mixing Hexagonal Architecture - aka Ports and Adapters - and Onion Architecture together.

Main objective is to uncouple and untangle concerns and underlying technologies. By proceeding this way, we avoid 2 main pitfalls of monolithic legacy architecture:

  • Leak of business logic outside the application core (Domain Model + Application layers) which tends to anemic core and single-use code anti-pattern. E.g., if you end up with business code inside your view models, you probably doing thing wrong.
  • Leak of peripheric concerns (Presentation & Infrastructure) via underlying libraries or technologies. E.g., if you end up with particular 3rd party notion inside your core, you lack a proper abstraction.

A 3rd party product or 3PP is a piece of code that you leverage to deliver your application, but that does not belong to your team/scope. And it applies to other company products or facilities as well, not only to external resources.

Last two layers live both at an underlying level, thus spreading all-over beyond the previous ones:

  • Foundation is the place to put everything you consider as elementary bricks for the application. E.g., custom data structure, plumbering around bare-metal concepts like thread are the perfect match. It is often things you end up with the idea they should be part of the language/framework.
  • Cross-cutting is dedicated to host cross-cutting concerns within Aspect Oriented Programming paradigm. There live Security, Logging, Exception/Error handling, Authentication, Authorization, Configuration, Caching, Monitoring, Communication, … The idea is to enhance application coherency and robustness by providing a standard way to address those concerns. It is de-facto a structuring layer and thus a key one.

Dependency flow

Layers obey a strict rule regarding interactions: the dependencies flow goes one-way, from outer to inner sticking to Dependency Inversion Principle, one of the 5 SOLID pillars. This enforces smart abstractions & proper partition.

You have to leverage two kinds of interfaces to achieve this pattern:

  • Application Programming Interface
  • Service Provider Interface

They served a common abstraction purpose with a twist:

  • For API you define both contract & implementation inside your core but you expose the only contract to the outside. E.g., you embed some business logic inside a service which will be in-turn exposed & consumed by a view model via its lone interface.
  • For SPI you define the contract & use it inside your core. It is the responsibility of an outsider to provide a compelling implementation. E.g., you define the contract for a persistence service and use it in your core, then you provide an implementation for every technology you want to support.

Keep in mind you have to stick to Interface Segregation Principle & Single Responsibility Principle - another two SOLID pillars - to avoid header interface and wrapping wool in cotton anti-patterns. This way of thinking about dependencies is called Ports & Adapters.

By introducing smart abstractions, you not only protect your code to be infected with 3rd party technologies or constraints, but you can go far ahead - with help of Mocks - without finalizing any technology choices, keeping all doors open.

Program flow

Interacting with an application through UI/IT/… can thus be schematized by this kind of flow:

  1. Starting from the left side i.e., the Presentation, hitting the Core through API and triggering Infrastructure through SPI
  2. Then ingesting Infrastructure side-effects into Core through SPI, and notifying Presentation through API accordingly.

This schema highlights the fact that:

  • Neither Presentation nor Infrastructure are aware of each other. They only know about the Core.
  • The business use case is materialized within the Core, and thus could be triggered via different ways & leverage different underlings.

Bounding contexts

Remember where we started from

Applying what we have learned so far allows us to reshape code base like this

By introducing layers and conventions we untangle a bit the blob structure. We now have split it into 6 parts namely layers. It is time to go further, time to introduce some semantic meaning aka Domain Driven Design (DDD) Bounding Context.

Primary objective is to introduce Ubiquitous Language. Simply said Product Owner and development team have to work together and do so by speaking the same language. Why do we talk about a single Cosim context development side when we use Vehicle Dynamics and Traffic management contexts business side? ​​​​​​​It not only noises interaction by introducing uncertainty but can lead to use case issues.

  • Can we ensure that use case involving only Traffic management will optimally perform i.e., without instantiating or losing time in not used Vehicle Dynamics part?
  • Can we handle use case where Vehicle Dynamics is provided by a 3rd party and Traffic management by another one? Idea is to shape source code by introducing bulkhead between orthogonal traits while smoothing twin-one interactions. This allow us to better cluster our inner Core:

Then we can even gather matching Presentation & Infrastructure concerns:

Closing

So, when you need to get rid of blob anti-pattern and evolve to a smarter architecture such as micro-services, this is usually done forwarding a 3-stages morphing process:

  1. Dispatching code into layers
  2. Extracting bounding contexts
  3. Evolving to a micro-services ecosystem

And hexagonal architecture is definitively a lucky draw to start your journey.

Side notes

Some may have noticed we talk about hexagon and draw octagon…

  • When first draft, initial purpose of the hexagonal architecture was not to suggest that there would be six borders/ports, but to leave enough space to represent the different interfaces needed between the component and the external world. Octagon shape has some subjective objective advantages over its hexagon siblings, like symmetry and slightly smarter space usage. And hexagon is also widely used for micro-services, that can lead to confusion.
  • Onion architecture - part of the same pattern family - only uses circles, which is fine but needs more effort to support discussion, e.g., the first circle, the second one, …
  • Should I also mention that once you deliver all materials sticking to a sketch-first approach, it is both painful and time consuming to change your mind…? Of course, from a code-first perspective, it is just a matter of style at the end…

Hexagonal architecture could have been named bolt & nut architecture, ports & adapters could have been named locks & keys architecture, Onion architecture the Dante’s one (not sure for this one), … See below the trendy name, and capture the essence of the pattern. There are plenty of smart concepts living around that would have deserved more than the poor naming they were forged with. And yes, we talk about you, the “micro” family aka micro-services and micro-frontends…

At the very end, pick the analogy you are more comfortable with, i.e., that fits in your head AND that you can easily share with mates.

Licensed under CC BY-NC-SA 4.0
Last updated on Dec 09, 2019 00:00 UTC
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy