DEV Community

Cover image for Hexagonal architecture in Python
Szymon
Szymon

Posted on • Originally published at blog.szymonmiks.pl

Hexagonal architecture in Python

Intro

Hexagonal architecture is one of the styles of application architecture that you can use in your project.
In today’s blog post I would like to show you how you can build your application using a hexagonal architecture with Python.

Instead of focusing on the theoretical side (there are already enough of these articles on the web), we will take a more practical approach.

History

Originally hexagonal architecture was invented by Alistair Cockburn in 2005.
Then around 2008, Jeffrey Palermo invented something very similar called onion architecture.
And then, as the last one, around 2011, Robert C. Martin came up with his idea called clean architecture.

So if you hear the following terms:

  • hexagonal architecture (aka. ports and adapters)
  • onion architecture
  • clean architecture
  • screaming architecture

It’s generally about the same idea, with some minor variations.

Quick intro

Please consider the following diagram:

Image description 1

Let me quickly introduce you to all the concepts included in the diagram.
The best explanation will be a code example where all these concepts are applied simultaneously.
For the example please have a look at the section below.

Generally speaking, ports are responsible for communication with the outside world.
Usually, they are implemented as interfaces, whereas adapters are concrete implementations of our ports.

input port (aka. driver port or primary port) - exposes the application features to the outside world. It’s an entry point to our business logic.

input adapter - is a concrete implementation of how we want to enter our application, for example via REST endpoint etc.

output port (aka. driven or secondary) - it is used to interact with outbound things, for example, reading/writing data from/to a database.

output adapter - it is the concrete implementation of the above. For example, implementation of communication with a specific database, e.g. MySQL.

use case (aka. application layer) - this layer answers the question "what to do". It controls the flow.

domain - in contrast to the above layer, this one answers the question "how to do".
Here our business logic lives. This is the place where our application makes money. This is the heart of our app.

As you can see on the diagram the use case layer knows what to do but is not aware of how to do it.
The use case layer delegates it to the domain layer.
Because of this, the domain layer is not aware of any ports or adapters.
The domain layer should not have any dependencies to the layers above it.

Example

For the purpose of this article I created a separate project on my GitHub. You can find it here:
https://github.com/szymon6927/hexagonal-architecture-python

This project is a simplified gym management software. We have clients, gym classes and gym passes.

The project consists of four modules:

Image description 2

  • building_blocks - contains all utilities used across different modules
  • clients - module responsible for clients. There is no complicated business logic here, but we have a lot of integrations that's why we use hexagonal architecture here
  • gym_classes - simple CRUD responsible for management of gym classes. No business logic here, therefore there is no need to use hexagonal architecture here
  • gym_passes - management of gym passes, which is the core of our business. This is where our business is making money

Image description 3

There is no fancy structure for the gym_classes module. The answer is simple - there is no need for that.
We should use hexagonal architecture only where it's really needed.

I developed this example specifically to show you that using hexagonal architecture doesn't mean using it everywhere.
Not every module has to have it.

This is also visible in tests.

Image description 4

There are no unit tests for the gym classes module because unit tests for it would make no sense.
All we do here are CRUD operations.

When it comes to modules that were built using hexagonal architecture. They follow such structure:

  • application - contains our use cases together with DTOs and other classes that are responsible for coordinating the business processes
  • domain - contains all domain objects like entities, value objects, etc. The objects there are not anemic, but they follow the principle of the rich domain model which means that we encapsulate data and behaviours together. You can check it by looking at src/gym_passes/domain/gym_pass.py
  • infrastructure - contains output adapters, which are objects responsible for communication with external world, for example database
  • bootstrap.py - contains the definition of our DI container. I wrote a separate article about the DI. If you have not heard about a technique called "dependency injection" you can read about it here
  • controllers.py - definition of our REST endpoints
  • facade.py (optional) - if your module needs to expose some behaviours for other modules then I recommend using a facade to achieve it. It is the public API of your module

Pros and Cons

Pros:

  • testability - you can test your domain logic without any dependencies
  • ability to postpone some decisions - when your project starts you don't have to know which database you will use. You can make this decision later on
  • it supports easy technology change - you can change your REST adapter to a gRPC adapter without touching your business logic/core domain

Cons:

  • if your project has multiple adapters it means that you will have more integration tests. This may impact the total execution time of your test suite
  • it’s harder to navigate through the project with such architecture. You will mostly encounter interfaces instead of real implementation
  • additional effort is needed to configure the adapters. You need to have some mechanism that will specify: this adapter is on prod env, this on local env, etc

When to use / when not to use

If you are wondering about cases to which hexagonal architecture suits the best, I prepared a simple heuristic for you.

Use if:

  • your project has complex and frequently changing business domain
  • you need to implement the core domain of your application

Don't use if:

  • your project or module has a CRUD-like complexity - in such a scenario, there will be no benefits of hexagonal architecture, and it will be overengineering

Additional resources

Summary

I hope you enjoyed it.
As with everything in our industry, hexagonal architecture is not a silver bullet.
It will not solve all the problems for you,
but it's a great way to improve things in your project especially when it comes to testability or the possibility to postpone some decisions.
If applied correctly it may improve your project and make your life easier.

Let me know what you think, I would love to hear your opinion.
Also, if you have experience with hexagonal architecture let me know what your thoughts/feelings are about it.

Top comments (0)