DevelopmentBackend

Hexagonal Architecture.

29 MARCH 2019 • 8 MIN READ

Piotr Majcher

Piotr

Majcher

header picture

Introduction

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 greenfield project that has a well-known structure:

hexagonal architecture initial package structure

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 the 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 the domain, not the other way round. It’s worth mentioning 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 the 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 at how can we improve it to follow the hexagonal architecture approach:

Now the first meaningful part of the package (skipping the 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 explicit package structure

Hexagonal Architecture in a 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 final package structure

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.