A practical example of applying Java Modules in a Hexagonal Architecture
1. Overview
In this tutorial, we’ll implement a simple Java application applying hexagonal architecture design principles.
Additionally, we’ll organize our application layers using Java Modules, which requires JDK version 9 or higher.
With this approach, we’ll decouple our core business logic and aim to improve understandability and maintainability.
2. Hexagonal Architecture
The term hexagon is used in this context just to describe the core of the application, it’s business logic.
The edges of the hexagon are viewed as input and output. A user interface or API calls are common examples of an input edge. A database connection or HTML code are some output examples. The main idea is to isolate the business logic, in a way that it’s framework agnostic and contains no outward dependencies.
Another synonym used for hexagonal architecture is ports and adapters pattern. Ports act like gateways or interfaces for interacting with the application core. Adapters bridge between the application core and the service needed by the application. Let’s now examine this pattern on a simple example.
3. Application Layers
We’ll divide our codebase into three layers based on hexagonal architecture principles: application (input), domain (business logic), and infrastructure (output).
The application layer defines how users or other software interact with our application.
The domain layer is the core of our application. It will contain only business logic and we’ll isolate it from other layers. Our aim is to simplify the reuse of our application core with different user interfaces, frameworks, and storage in the future. In Java, we can achieve this by defining interfaces in the domain layer.
The infrastructure layer will implement all infrastructure-dependent interfaces defined in the domain layer, as well as any framework-specific classes.
We’ll use a Maven multi-module project for this tutorial and leverage on Java modules to isolate the layers.
5. Domain Layer
Let’s begin by defining our business logic in the domain layer.
We’ll keep things very simple in this tutorial and create an application for storing some basic information about books.
Firstly, we’ll create our Book domain data class. To keep well-known code to a minimum, we’ll use Lombok annotations:
Next, we’ll create a domain service interface for our business logic.
At a later point, any user interface, that would like to use our application core, can reference this domain interface.
The domain interface will act as a port in our hexagonal architecture:
Let’s implement our domain service based on the interface.
Again, we keep things very simple in this tutorial and support only create and delete operations:
As you might have noticed, our service implementation has a dependency on a repository for storing book information.
Let’s define a repository interface definition in our domain layer. This is another port in our hexagonal architecture.
Since saving data into a repository contains no business logic, the implementation of the interface will be provided in another layer:
Next, we’ll provide a factory class for our domain service creation, so other layers can easily create an instance of the service, without us exposing its implementation:
Because our domain layer is completely isolated, we can also test it independently using Mockito and JUnit 5:
To wrap up the domain layer definition, we’ll define a Java module for the domain layer by creating a module-info.java file.
It’s important to note that we are exporting data classes and interfaces, but not the service implementation.
6. Application Layer
In this section, we’ll implement the application layer to allow users to communicate with our application core.
Here, we could implement a Web interface or a REST controller. Again, to keep this example simple, we’ll let users interact with the application only via console:
Our application layer Java module has only one internal dependency on the domain layer:
6. Infrastructure Layer
The infrastructure layer will contain the code needed to actually run the application.
Let’s start by implementing the repository interface defined by the domain layer:
The repository implementation has an infrastructural dependency on a database, in this case, its MySQL:
We seem to have everything in place, except a way to run our application.
As the last step we’ll define the main class and a method to start the application:
As the infrastructure layer hooks the application together, it contains internal dependencies on both other layers.
It exports only the package containing the main class so that the application can be started. Note that no other infrastructure implementation packages are exported:
Now we are ready to build the application using Maven command: mvn clean package.
Maven will create an executable JAR for us and we can start the application by running: java -jar infrastructure/target/infrastructure-1.0-jar-with-dependencies.jar
7. Conclusion
By following hexagonal architecture design principles we separated our application into three layers: domain, application, and infrastructure.
The first obvious advantage of this approach is that each layer/module focuses on its own logic. This makes the whole application naturally easier to understand.
The second big advantage is that we’ve completely isolated the domain logic from everything else. Our domain logic is not tied to any specific framework or storage implementation. Thus, it can be easily migrated to a different environment.
In case you would like to extend the application by implementing a REST controller, you only need to change the code in the application layer. In case you would like to replace the MySQL database with MongoDB, you only need to change the code in the infrastructure layer. Finally, if you would like to make changes to the business logic, you don’t need to worry about user interfaces, frameworks, or storage infrastructure.
In this tutorial, we demonstrated how to implement the hexagonal architecture in core Java, without any frameworks, but using Java modules (project Jigsaw). The code for these examples is available over on Github.