Hexagonal architecture tutorial: Build maintainable web apps

When designing an effective web application, it’s important to get your software architecture right. A great way to build a maintainable web application is to build an architecture that is flexible, extensible, and adaptable. Hexagonal architecture is a popular architectural pattern in software development. This style of architecture promotes the separation of concerns by putting logic into different layers of the application. Today, we’re going to dive deeper into the hexagonal architectural pattern and discuss principles, pros and cons, use cases, and more.

Let’s get started!

We’ll cover:

  • What is hexagonal architecture?

What is hexagonal architecture?

Hexagonal architecture, or ports and adapters architecture, stems from the work of Alistair Cockburn. It’s an architectural pattern used for designing software applications. With hexagonal architecture, we put our inputs and outputs at the edge of our design. This allows us to isolate the central logic of the application from the outside world. Since our inputs and outputs are on the edge, we can switch their handlers without affecting our core code.

Hexagonal architecture aims to increase the maintainability of our web applications so that our code will require less work overall. Hexagonal architecture is represented by a hexagon. Each of the different sides of the hexagon represents different ways that our system can communicate with other systems. We could communicate using HTTP requests, a REST API, SQL, other hexagonal architectures, etc. Each layer of the hexagon is independent of other layers, so we can make individual changes without affecting the entire system.

Let’s take a look at what a hexagonal architecture might look like:

The application layer is represented as a hexagon. Within the hexagon, we have our domain entities and the use cases that work with them. As we can see, there are no outgoing dependencies. All of our dependencies point towards the center. The inside of the hexagon, or the domain, depends on nothing but itself. This ensures that the business logic is separated from the technical layers. It also ensures that we can reuse the domain logic. If we change our stack, it will have no impact on the domain code. The core holds the primary business logic and business rules.

Outside of the hexagon, we see different adapters that interact with our application. Different adapters will interact with different aspects of the application. For example, we could have a web adapter that interacts with a web browser, some adapters that interact with external systems, and an adapter that interacts with a database. The adapters on the left side drive our application because they call our application core. The adapters on the right side are driven by our application because they are called by our application core.

Adapters are either external APIs of your application or clients to other systems. Adapters use ports to initiate interaction with the application. A REST controller would be an example of an adapter. The application core provides ports so it can communicate with the adapters. Ports allow us to plug the adapters into the core domain. We can think of ports as agnostic entry points.

Note: Hexagonal architecture shouldn’t depend on any technical framework. This includes external annotations such as Java Persistence API (JPA) and Jackson.

Benefits over traditional layered architecture

Hexagonal architecture was a departure from the traditional layered architecture. One of the major differences with hexagonal architecture is that the user interface can be swapped out. There are many benefits to using hexagonal architecture instead of layered architecture. Let’s look at some of the pros, cons, and use cases:

Pros

  • Maintainability: Our applications have high maintainability because changes in one area of our application don’t affect other areas.

Cons

  • Decoupling: The performance of our application could be affected because of intermediate classes.

Use cases

Some example use cases for hexagonal architecture include:

  • A banking application that allows us to send money from one account to another

Principles of hexagonal architecture

Now, let’s take a look at some of the underlying principles behind hexagonal architecture.

Single Responsibility Principle

The definition of the Single Responsibility Principle is “a component should have only one reason to change.” When related to architecture, this means that if a component has only one reason to change, we don’t have to worry about this component if we change the software for any other reason.

Dependency inversion

The dependency inversion principle (DIP) allows us to invert the direction of any dependency within our codebase. The catch is that we can only invert dependencies when we have control of both sides of the dependency. So, if we have a dependency on a third-party library, we can’t invert it because we don’t control the code of the library.

Let’s walk through the dependency inversion principle in action. Let’s say we want to invert the dependency between our domain code and our persistence code so that our persistence code relies on the domain code. We’ll use the following structure:

In the above structure, we have a service in the domain layer that works with a repository and an entity in the persistence layer. We can create an interface for the repository in the domain layer and let the repository in the persistence layer implement it. This allows us to free our domain logic from its dependency on the persistence code. This is what it would look like:

Isolate boundaries using ports and adapters

Ports and adapters allow us to run our application in a fully isolated mode. Hexagonal architecture uses ports and adapters to illustrate the communication between the inside and the outside. Ports are the boundaries of our application. There are two kinds of ports: primary and secondary.

Primary ports, or inbound ports, are the initial communication points between the outside world and the core of the application. Primary ports are where requests come through to the application. Secondary ports, or outbound ports, are used by the application core to upstream data to external services.

Adapters serve as the
implementation of our ports. There are two kinds of adapters: primary and secondary. Primary adapters are implementations of primary ports. They are independent of the core of the application. Secondary adapters are implementations of secondary ports. They are also independent of the application core.

Example of hexagonal architecture

Earlier in the article, we listed a few hexagonal architecture use cases. Now, we’ll begin working with one of those use cases in a short tutorial. We’ll follow the use case of an application that allows us to send money from one account to another. Let’s take a look at a teaser for the code we’ll write to create the SendMoneyService class:

package buckpal.account.application.service;

@RequiredArgsConstructor
@Transactional
public class SendMoneyService implements SendMoneyUseCase {

private final LoadAccountPort loadAccountPort;
private final AccountLock accountLock;
private final UpdateAccountStatePort updateAccountStatePort;

@Override
public boolean sendMoney(SendMoneyCommand command) {
// TODO: validate business rules
// TODO: manipulate model state
// TODO: return output
}
}

What to learn next

Congrats on taking your first step toward using hexagonal architecture! The hexagonal architecture software design pattern creates an abstraction layer that isolates the core of the application from external tools and technologies. It’s a popular architectural style within software development. Some recommended topics to learn next include:

  • HTML

Happy learning!

Coding is like skateboarding: you can’t learn new skills just by watching someone else. Master in-demand coding skills through Educative’s interactive courses.