Categories: Architecture, Programming
A friend and colleague of mine was recently praising the Hexagonal Architecture design pattern. I hadn’t heard of it so did a little research.
My conclusion is: it’s a very nice consistent description and set of names for something that already exists and I would refer to as a sane layered architecture.
The pattern is applicable to modulith codebases, ie applications which are of a non-trivial size and implement a significant amount of business logic. It doesn’t really apply to small code-bases (whether simple applications, or simple component services in a complex distributed system). And it doesn’t really apply to purely technical projects, eg a message-broker or graphical-interface-framework.
Note that I don’t mean modulith in any negative sense. Distributed systems (eg the micro-services pattern) can be very valuable in their place, but they also have a cost; if you have a problem that can be solved with a single integrated codebase and without running into issues related to developer conflicts, testing, or release problems, then IMO a monolithic system is the right answer. And it should be internally modular - ie a modulith.
The hexagonal architecture pattern sets guidelines for creating that modular structure. In particular, it puts the domain model at the center of the application. That domain model should be pure, concentrating on representing the types and processes that the business experts are familiar with. All technical issues such as persistence and network communication should be excluded from this layer/module. Then “adapter” modules are built around that core to connect it to the real world: typically one persistence adapter and one or more remote-api adapters. Adapters which call into the domain module (eg offer a remote-api) are called “input adapters” while adapters which are called by the domain model (eg persistence) are called output adapters.
Input adapters call services defined by the domain module, passing parameters which are types defined by the domain module. For output adapters, the domain layer defines the interfaces it needs (which accept and return types defined in the domain model) and the output adapter module implements those interfaces. In this way, no types from the adapter modules are ever need-by/exposed-to the domain module; any type-mappings are done in the adapters only.
Given that a non-trivial monolithic application is being developed, it might be the case that the single code-base actually implements multiple problem-domains. Each such domain should be its own module, ie there is no need to force-merge domain modules together just because they are in a single codebase/repository/deployed-artifact.
Separate modules can mean something like different maven artifacts/jarfiles for a Java-based application, or could simply mean placing code in distinct namespaces (eg Java packages). In any case, a tool such as jqassistant/ArchUnit can be useful to ensure that the desired modularity isn’t accidentally violated.
This of course results in a technically layered architecture, where different technologies (rest, JPA, etc) are separated. It doesn’t prevent further modularity being applied to each module (input-adapter, output-adapter, domain-module), just sets a base structure.
This layout does simplify the process of adding new remote interfaces (eg message-driven, gRPC, GraphQL) and potentially new persistence alternatives.
And IMO the focus on a clean domain module greatly improves the readability and testability of the codebase.
The hexagonal architecture isn’t a revolution; the benefits of a clean/pure domain model have been known for a while. However it gives a clear definition/description of the desired structure and provides good names for things. And good names are very helpful - it’s the core of any design pattern. So thumbs-up from me.