Hexagonal Architecture.

Hexagonal Architecture.

Removing technical debt step by step.

Posted by Piotr Majcher on March 29, 2019

Hexagonal architecture is nothing new. We can achieve its main principle by using basic language mechanisms and a proper package structure. On top of that, it’s relatively cheap to do that. Especially when we compare it to the cost of the potential technical debt that might appear because we failed to follow good practices when developing our IT project. Still, hexagonal architecture isn’t a very popular technique today. So, let’s start with some sample legacy code and see how we could refactor it to follow the hexagonal architecture principles.

Note: In this example, we use Java and Spring but this technique is language and framework agnostic.

From plain old sample project to Hexagonal Architecture

To make our sample project simpler, let’s assume that we’re developing an application that processes a single resource - messages. We’re exposing one method over HTTP: get all messages. We can start now with the green field project that has a well-known structure:

Hexagonal Architecture

Inside you’ll find some very simple code. Let’s start from the service, our core component:

@Service
public class MessageService {
   private final MessageRepository messageRepository;

   public MessageService(MessageRepository messageRepository) {
       this.messageRepository = messageRepository;
   }

   public List findAll() {
       return messageRepository.findAll();
   }
}

and then repository:

interface MessageRepository extends MongoRepository {
}

Dependency Inversion

What’s wrong with that? Our message service uses MessageRepository directly, so it depends on it. It breaks the last SOLID principle - Dependency Inversion. How can we get rid of this dependency? Here’s the answer: we can invert it. Let’s define the interface in a core (service) package.

public interface MessageRepository {
    List findAll();
}

We can then implement it in persistence package using files, MongoDB or any other storage. Now the persistence - the low level technical component - depends on a high level abstraction.

Driven port

What did we just do from the Hexagonal Architecture perspective? We have created a secondary (driven) port - MessageRepository - and a secondary adapter - for instance, MongoMessageRepository. Now persistence depends on domain, not the other way round. It’s worth to mention here that MessageRepository acts as Service Provider Interface (SPI).

Driver port

We already saw the secondary (driven) port and adapter. What about the primary one? The primary (driver) port serves as the definition of API domain exposed by the domain to external world. In our case, it’s the MessageService. Perhaps MessageFacade sounds better? But the name isn’t that important - rather, it’s the fact that this is the only entry point to the domain. The API is called by the primary adapter - in our sample, the MessageController. To summarize:

The primary port is API and it’s called by the primary adapter.
The secondary port is SPI and it’s implemented by the secondary adapter.

What else could we improve?

Package structure

Packages are a powerful feature of Java. The package structure should tell us something about the project. What we can say based on the package structure from picture 1 at a first glance? No more than the fact that we use layered architecture. Let’s take a look how can we improve it to follow the hexagonal architecture approach:

Hexagonal Architecture

Now the first meaningful part of the package (skipping company’s domain and project name) contains information about the main business concept - the message service. At this stage, it’s not important if we have controllers and persistency; these are technical details. Digging deeper, we see two main packages - the domain containing entire logic and adapters; the external components dependent on the domain.

Hexagonal Architecture in real project

The example showed previously is a bit verbose. If you know which component plays which role, the package structure can be simplified and still provide the separation required by hexagonal architecture.

Hexagonal Architecture

Summary

Keeping the core domain separated is crucial, especially for larger projects. If you’re working on a simple CRUD, perhaps the layered architecture is all you need. On the other hand, the goal of providing hexagonal architecture forces developers to think about the boundaries in the application. And that’s always good. Even if the application they’re working on doesn’t require sophisticated architecture, keeping these concepts in mind is always beneficial in the long-term perspective.

Let`s work together!

+48 538 365 618