DEV Community

Cover image for Hexagonal architecture tutorial: Build maintainable web apps
Erin Schaffer for Educative

Posted on • Originally published at educative.io

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?

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:

Alt Text

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.
  • Flexibility: We can easily switch between different applications, and we can add new adapters without changing the source code.
  • Simple testing: Since our code is separated from the implementation details of the outside, we can test in isolation.
  • Agnostic: Since the application is independent of external services, we can develop the inner core before building external services.

Cons

  • Decoupling: The performance of our application could be affected because of intermediate classes.
  • Debugging: It can sometimes be hard to understand and debug adapters.
  • Complex: Hexagonal architecture can sometimes be confusing because it’s not always obvious what we should consider to be on the outside.

Use cases

Some example use cases for hexagonal architecture include:

  • A banking application that allows us to send money from one account to another
  • A system that allows us to apply for a loan, go through verification, and receive an update when our application updates
  • A loyalty application that allows us to register customers and upgrade or downgrade their memberships

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:

Alt Text

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:

Alt Text

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
 }
}
Enter fullscreen mode Exit fullscreen mode

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:

To get hands-on with hexagonal architecture and to finish building our “Send Money” use case, check out Educative's course Hexagonal Software Architecture for Web Applications. In this curated course, you’ll learn how to design software modules and applications in a clean and maintainable way. By the end, you’ll have a deeper understanding of how to implement software architecture.

Happy learning!

Continue reading about software architectures

Top comments (0)