Hexagonal architecture in Rust

Luca Corsetti
5 min readFeb 8, 2023

--

Hexagonal Architecture — Rust

Tutorial index

Introduction

In this tutorial we will implement a Rust application using DDD and we will organize layers leveraging Hexagonal Architecture.
In order to reach a reliable code we will proceed with the implementation using TDD.

We will implement the CRUD required to supply a simple service of sandwich recipe management. This will also give us the opportunity to illustrate basic REST principles.
After this implementation, we’ll go through 2 simple examples to show how hexagonal architecture can isolate our domain logic from the external world.
In the end, we will containerize our application and we will enrich our project with a micro-service architecture deployed on Kubernetes.

Prerequisites

  • basic Rust knowledge
  • basic REST knowledge
  • basic TDD knowledge

Technologies

Language: Rust

Web framework: Actix

Architectural style: REST

Persistence layer: MongoDB

Hexagonal architecture

Hexagonal architecture is an architectural pattern that aims to create loosely coupled application components.
The basic principle is the separation of the domain application logic from the external world and the creation of adapter components to connect them together.
As you can grasp, hexagonal architecture and DDD (domain-driven design) work very well together because of the central role played by domain logic.

Hexagonal architecture diagram

Principles

Despite what the name may suggest, hexagonal architecture can be broken down into 4 kinds of components:

  • domain
  • use cases
  • ports
  • adapters

Domain

The domain layer comprises all those objects modelling the application domain and containing the application logic.
To better adhere to the principles of DDD, we should design these objects by trying to trace their names and interactions to their real domain counterparts. This lower abstraction leads to a better understanding of the domain and better synergy with the domain experts who usually provide the application requirements.
The domain is completely agnostic of external behaviours and doesn’t have any outward dependency. This helps us to guarantee the Single responsibility principle (the S in SOLID) ensuring that we can change the business logic only to implement change requirements and not to align to changes external to the system.

Use cases

Use cases are a further abstraction over the classic service layer and often they are designed separately to be then incorporated into the same implementation object.
They represent particular use cases to manage in the application flow (e.g. a search over some data, a money transfer, a recipe registration, etc.), they contain coordination logic to apply to the domain objects and they are useful to separate functionalities in small and well-defined pieces of code.
Furthermore, they act as an interface between the external world and the domain logic to guarantee, again, the independent evolution of the domain layer.

Ports

Ports are those components responsible for communicating with the external world, so with everything that lives outside of the hexagon.
A port can be a simple rule defining a contract between the outside world and our application.

Ports are divided into 2 groups:

  • input ports
  • output ports

Input ports, generally represented on the left of the hexagon, interact with driving adapters. They manage the requests incoming from the external world (any client querying our application) and generally are called by an adapter.
Output ports, generally represented on the right of the hexagon, interact with driven adapters, so-called because they are driven by our core. They ask for something from an external component (e.g. a database, external API, etc.) and generally are implemented by an adapter.

Adapters

Adapters are responsible for adapting external requests to the format expected (for input ones) or expressed (for output ones) by our ports.
An adapter can be anything (a REST controller, a web interface, a command line application, a persistence manager, etc.) that is able to communicate with a port or respect its contract.
It is easy to understand that for each port we can have multiple adapters. This really looks like the `trait` concept in Rust, so we will leverage it in this tutorial.

Adapters are divided into 2 groups:

  • driving adapters
  • driven adapters

Driving adapters drive the execution of our core functionalities by requesting something to an input port.
Driven adapters are queried by our ports in order to communicate with an external service.

Pros and Cons

Let’s have a look at the positive and negative implications of this architecture.

Pros

Domain isolation

By encapsulating the domain logic within the hexagon, we can ensure that the core of our logic is independent of the rest of the application. If at some point we need to change something outside the hexagon, we can safely apply our changes without worrying about the domain.

Flexibility

The hexagonal architecture enhances the flexibility of an application. The core of this feature lies within ports and adapters: by defining interaction contracts between the outside and inside of the hexagon we can switch between different adapter implementations without affecting the rest of the application.

Focus on the business logic

By isolating our domain logic, we can focus on the core business logic paying the most of our attention to it, deferring the remaining decisions at the end, when they are required.

Ease of development

By defining contracts between the components, it becomes easier to divide the application into parts that can be developed simultaneously by different developer teams.

Testability

By separating the application components, it becomes easier to test them. This is achieved with different techniques, but the most typical is often the use of doubles (as we will see in the next chapters).

Cons

Complexity

The pros listed above come at the cost of increased complexity. The whole system increases the number of its components in order to specify interaction contracts and this can easily lead to bigger software.

High number of data models

To separate each part of the hexagon, we increase the number of data models to communicate with each other and this can lead to confusion.

Conclusion

In this article, we have introduced the main principles of hexagonal architecture.

In the next chapter, we will begin creating a web application starting with its domain logic.

--

--

Luca Corsetti

Curiosity shows me the way, passion propels me on a journey. The journey is the next road on the horizon. Abandon the boot, keep sailing.