If you are familiar with the layers architectural pattern, it is only a small step to the even nicer hexagonal pattern. Here is an example of how you can do it.

Intro

This document will show that a hexagonal architecture can be viewed as an extension to the layered architecture pattern. It adds a neater separation of your own business logic and interfaces (aka. ports) to the outside world. Since it is an extension to the layers pattern it is only a small step to the hexagonal pattern for those already familiar with the layers pattern.

It is recommended to be familiar with both patterns before reading on (see References below for more on that).

In the layered pattern the service layer module is kind of a bucket for all we cannot put in either the presentation or the access layer. The service layer usually also depends on the access layer compile time, making it harder to replace it with another access layer implementation runtime. Now this can be avoided still using the layers pattern, using the hexagonal pattern this comes more natural.

The example below illustrates that the Ports module defines all inbound and outbound ports. All business logic is encapsulated in the Core module. Ports + Core do not depend on any other module, not even the DrivenAdapters module. Instead, the DrivenPorts in it defines the outbound interfaces it requires, implemented by the DrivenAdapters module, which can be runtime injected. The separation of concerns is neatly reflected in the module structure.

My System

Consider the following system:

system.png
Figure 1. My System

Hexagonal Architecture

The hexagonal architecture now prescribes you should use the following components:

hexa.png
Figure 2. Hexagonal Packages

Mapping to Layers

To illustrate the link with the layers pattern we can now use the diagram above to map the hexagonal packages onto layers:

  • DrivingAdapters → Presentation layer

  • Ports + Core → Service layer

  • DrivenAdapters → Access layer

Dependency Inversion

To enforce dependency directions use a separate Maven/Gradle module for each package. That works because Maven/Gradle do not allow circular dependencies. Alternatively you could also enforce these rules with tools like ArchUnit or Spring Modulith.

Modules and Code Packages

Now the following Maven/Gradle module and code packages could be used.

DrivingAdapters module

org.myorg.mysystem.adapters.driving.api1
org.myorg.mysystem.adapters.driving.api2
org.myorg.mysystem.adapters.driving.api5
org.myorg.mysystem.adapters.driving.api1.model
org.myorg.mysystem.adapters.driving.api2.model
org.myorg.mysystem.adapters.driving.api5.model

Ports module

org.myorg.mysystem.ports
org.myorg.mysystem.ports.api3
org.myorg.mysystem.ports.api4
org.myorg.mysystem.ports.api6
org.myorg.mysystem.ports.service
org.myorg.mysystem.ports.api3.model
org.myorg.mysystem.ports.api4.model
org.myorg.mysystem.ports.api6.model
org.myorg.mysystem.ports.service.model

DrivenAdapters module

org.myorg.mysystem.adapters.driven.api3
org.myorg.mysystem.adapters.driven.api4
org.myorg.mysystem.adapters.driven.api6
org.myorg.mysystem.adapters.driven.api3.model
org.myorg.mysystem.adapters.driven.api4.model
org.myorg.mysystem.adapters.driven.api6.model

Core module

org.myorg.mysystem.core

Application module

org.myorg.mysystem.application
org.myorg.mysystem.config

Utils module

org.myorg.mysystem.utils
shadow-left