Overview of Hexagonal Architecture
An overview of hexagonal architecture, or otherwise known as Ports And Adapters Architecture, and one way to implement it yourself.
Join the DZone community and get the full member experience.Join For Free
Building applications applying the Layered Architecture's is usually considered as simple and effective. The typical problem with layers is where is the application core logic? Is it the database? Maybe it's the business logic with some little things scattered over to the presentation layer. Maintainability is at the heart of any good software design. Codebases that are not well maintained is difficult to manage.
In 2006, Alistair Cockburn coined the term Hexagonal Architecture that tackles these issues by building the application around the core. This architecture is also known as the Ports And Adapters Architecture.
As the name ‘Ports And Adapter’ suggests, it defines ports which are means through which interaction takes place. Adapters encapsulate the logic to interact with external systems. A port is an entry point, provided by the core logic. It defines a set of functions. An adapter is a bridge between the application and the service that is needed by the application. It fits a specific port.
The main principles of Hexagonal Architecture are:
- Outer components depend on the domain and service layer, not vice versa
- Application, domain, and infrastructure are separate
- Integrate components into the core through ports and adapters
Using this port/adapter design, with our application in the center of the system, allows us to keep the application isolated from the implementation details like ephemeral technologies, tools, and delivery mechanisms, making it easier and faster to test and to create a reusable proof of concept.
We have an application that uses DB2 for database interactions and we want to switch to MongoDB to store data and provide results.
Using a traditional approach, we will use that library classes directly in our code base, as type hints, instances, and/or superclasses of our implementations.
Ports and adapters approach
Using ports and adapters we will create an interface, let’s call it BookPersistencePort, which we will use in our code when needed as a type hint. We will also create the adapter for MongoDB, which will implement that interface, let’s name it BookMangoDbAdapter. This implementation is a wrapper for the MongoDB library, so it gets the library injected and uses it to implement the methods specified in the interface.
At some point, we want to switch from MongoDB to Cassandra. Moreover, for the same query use case, sometimes we want to use MongoDB, and other times we want to use Cassandra, with that decision being made at runtime.
If we used the traditional approach, we will have to search and replace the usage of the MongoDB library for the Cassandra library. However, that is not a simple search and replace: the libraries have different ways of being used, different methods with different inputs and outputs, so replacing the libraries will not be a trivial task. And using one library instead of another one, at runtime, won’t even be possible.
However, if we used Ports & Adapters, we just need to create a new adapter, let’s name it BookCassandraAdapter, and inject it instead of the MongoDB adapter. To inject a different implementation at run time, we can use a Factory to decide which adapter to inject.
In the above examples, testing becomes easier with Ports and Adapters Architecture. In the first examples, we can mock or stub the interface (Port) and test our application without using Cassandra nor MongoDB.
Similarly, we can test all the UIs in isolation from our application, and our use cases in isolation from the UI by simply giving our service some input and asserting the results.
There are many ways to implement a hexagonal architecture. I showed you a straightforward approach that provides an easy to use, command-driven API for the hexagon. It reduces the number of interfaces you need to implement. And it leads to a pure domain model. The key take away is to isolate the business logic from the delivery mechanisms and tools used by the system. And it can be done using a common programming language construct: interfaces.
Opinions expressed by DZone contributors are their own.
The Role of Automation in Streamlining DevOps Processes
Opportunities for Growth: Continuous Delivery and Continuous Deployment for Testers
Merge GraphQL Schemas Using Apollo Server and Koa
Mastering Go-Templates in Ansible With Jinja2