Hexagonal Architecture

Jean Karty
Globant
Published in
6 min readOct 14, 2020

So many of us, especially those who have worked on large projects, know how difficult it is to make changes. This is because normally it affects other pieces of code. These problems not only apply to refactors, but also when introducing features. These changes take time and costs money.

We could say this happens because of a bad design, a lack of a good test suite, having tight release schedules, or even to inefficient collaboration between teams and team members.

Obviously there is no perfect solution for this, but you could prepare yourself for different scenarios from the beginning.

This architectural pattern shows how to tackle these problems by describing a set of concepts and rules. It introduces flexibility and a way to build a reliable test suite.

Background

Hexagonal Architecture is a layered architecture that describes best practices to a common occurring problem.

It is not new, it was introduced in 2005 by Alistair Cockburn, and has gained momentum over the years.

Many of us know about the Clean Architecture and the Onion Architecture, which are variants introduced years later by different authors.

The hexagonal figure was taken to represent different examples between the components, it has nothing to do with it having 6 sides. The formal name given by its author is actually called Ports and Adapters Architecture. Curiously there is an “Octagonal Architecture”, which is also very interesting.

It works well, especially on big applications, but it might be an overkill for short lived or small projects that won’t grow. It is best suited for projects that get their data from many sources and / or know that it can change.

It is not a framework or protocol dependent.

Changing inputs and outputs have a minor impact and almost no code rewrite.

One of the differences between the layer pattern and the hexagonal pattern is that the UI component is seen as an external system and can be swapped at any moment.

It is not the same as DDD, but they can reinforce each other.

Intent

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.

According to the above, we can deduce that the Hexagonal Architecture will let you build a highly maintainable application, reduce your technical debt and make integrations easily from the start.

Architecture

The Hexagonal Architecture is actually quite simple to understand and to implement.

Let’s go on by looking at a basic visual representation of Hexagonal Architecture.

As we can see it is divided by layers. You can find the ports at the edges of the inner hexagon.

In the outer layer are the adapters that communicate with external systems.

The arrows that are pointing to the center are to denote the dependencies. Dependencies go from the outside to the center. This is important because it states that the core application should not know specifics about how the adapters work. This ensures the isolation of the business logic.

In the image below we can see the difference to the n-tiered Architecture, where each layer depends on the layer below.

Let me now go on to describe what each component is and what it is meant to do.

Ports

These act as boundaries between layers.

These components are essential to architecture, and you can refer to them as interfaces.

We use interfaces when the application needs multiple implementations of the same type. Defines how layers can communicate with each other by specifying methods between implementations.

Ports can be used as inbound or outbound interfaces.

The ports ensure that the core application is independent of any input or output. It also makes swapping services easier to accomplish.

Adapters

The Adapters are implementations to a specific technology, protocol or system.

These can be inputs, as in the transport layer, or outputs as data sources.

The most common transport layer is HTTP API, but it can be a message bus, queue messaging, a console command, etc.

The data sources are where you get your data. Typical examples are databases, REST API, files, GraphQL API, etc.

Core Application

The core application is where the entities, behavior and constraints of your domain resides.

As you can see, by having a layer dedicated to the core functionality and the use of the ports, we isolated the business logic.

Supporting domain logic such as Use Case, Domain Event or logic through plain objects (POJO, PORO, POCO, etc.) go here.

Do not confuse Domain Event with a System Event, which are often used as hooks within a framework.

So let’s redraw the visual representation of the architecture to make these concepts a bit more clear.

As you can see, the arrows of the adapters point to the core application, as in the hexagon representation, the dependencies goes from the outside to the inside

If the interface for the data source was to be removed, the arrow would be pointing in the other direction. The business logic would not be isolated, making it dependent on the repositories. An example of this case is when an application places the ORM in the business logic. This would make it unimaginable, if not impossible, to change it after.

If the interface for the request interface was to be removed, the arrow would still be pointing to the core application. So why the interface? In our example we only have one input, but if we were to have more than one, the business logic would need to know the specifics of every input.

Also be aware that we would be violating the interface segregation principle, by forcing the adapter to depend on methods it wont use.

Testing

Ports

There is no need to test those. Interfaces don’t need to be tested.

Core application

So this layer does not have dependencies from the outer layers.

This is important, because you can test the business logic Independently from any external systems.

You can test in detail this layer!

I think it is important to stress the need for a good test that covers the behavior of the business logic.

We can use tools like dependency injection, mocks or any test double methods to replace the data sources.

Adapters

It is important to know that you can get the information you need for the core application.

There is no need to over test this part, but it is important to know what to do in case an exception is thrown.

For the inputs, it is recommended to test the whole system. In this way you can ensure that the whole application is connected as it should. In other words, do some integration testing. As we have already tested the core application thoroughly and tested the data sources, you can keep these kinds of tests to a minimum.

Conclusion

This type of software architecture will give you the flexibility needed for changes. By being framework independent it gives you the opportunity to try a better / new one or even upgrade between major versions with ease.

It will let you work on the business logic, the service layer and the data sources in parallel.

Remember to not include your ORM in the logic layer.

Even though it takes more work and it seems like you are duplicating things, in the long run it will be more beneficial.

Have better and simpler tests focus on what each layer needs.

Always look for how to do things differently and try to constantly defy what you know.

References

--

--