DEV Community

Anshul Bansal
Anshul Bansal

Posted on • Updated on

Hexagonal Architecture in Java

1. Overview

In this quick article, we'll discuss Hexagonal Architecture in Java through a practical example.

2. What is Hexagonal Architecture?

In simple words, a Hexagonal architecture separates software in two components - inside and outside, instead of conceptual layers.
The component that remains inside usually consists of application and domain layers, along with the core business logic. Whereas, the component for the outside world consists of layers like UI and Database.

The connection between the inside and outside components realizes via abstractions called ports and implementations called adapters.

3. Benefits

  • Flexibility - with different adapters, the software can serve multiple channels
  • Testability - as mocking code is easy
  • Purity - as the core logic remains intact

4. Hexagonal Architecture in Java

In Java, interfaces are ports and implementation classes are adapters.

Let's create a simple Spring Boot App and try to register/view Employee details using the Hexagonal Architecture.

To start with, we'll create an entity named Employee.
Then, the EmployeeService class to keep the core business logic to persist data.

Here, the Employee and EmployeeService classes are part of the inside component. Therefore, we'll create ports to expose them.

First, let's create the EmployeeUIPort interface to communicate with the front-end:

public interface EmployeeUIPort {
    @PostMapping("register")
    public void register(@RequestBody Employee request);

    @GetMapping("view/{id}")
    public Employee view(@PathVariable Integer id);
}
Enter fullscreen mode Exit fullscreen mode

Then, we'll create the EmployeeDBPort interface to communicate with the database:

public interface EmployeeDBPort {
    void register(String name);

    Employee getEmployee(Integer id);
}
Enter fullscreen mode Exit fullscreen mode

Last, we need adapters to connect to the ports - EmployeeUIPort and EmployeeDBPort.

Let's create the EmployeeControllerAdapter class that implements the EmployeeUIPort interface and act as a RestController. Therefore, it'll act as an adapter to connect with the front-end:

@RestController
@RequestMapping("/employee/")
public class EmployeeControllerAdapter implements EmployeeUIPort {
    @Autowired
    private EmployeeServiceAdapter employeeService;

    @Override
    public void register(@RequestBody Employee request) {
        employeeService.register(request.getName());
    }

    @Override
    public Employee view(@PathVariable Integer id) {
        Employee employee = employeeService.view(id);
        return employee;
    }
}
Enter fullscreen mode Exit fullscreen mode

Then, we'll create the EmployeeServiceAdapter (or EmployeeService for simplicity) class that implements the EmployeeDBPort interface and keep it as a Spring Service. So, it'll behave as an adapter to persist data in the database:

@Service
public class EmployeeServiceAdapter implements EmployeeDBPort {
    @PersistenceContext
    private EntityManager entityManager;

    @Transactional
    @Override
    public void register(String name) {
        Employee employee = new Employee();
        employee.setName(name);

        entityManager.persist(employee);
    }

    @Override
    public Employee getEmployee(Integer id) {
        return entityManager.find(Employee.class, id);
    }
}
Enter fullscreen mode Exit fullscreen mode

That's it, we've successfully separated our App into the inside and outside components.

As a result, the business logic of the app will be exposed via ports and consumed via adapters from outside.

Similarly, we can create different ports and implement multiple adapters for any core service.

For instance, Messaging Service and Content Management Service.

5. Conclusion

In this quick article, we've explored the concept of Hexagonal architecture in Java.

We've seen the core business logic (inside component) can be exposed as ports via interfaces and consumed by different adapters (outside component) through implementation classes.

This approach helps in writing clear, readable code with less dependency on external technology. However, I'd suggest using this pattern of ports and adapters selectively, rather than going full hexagonal.

Top comments (5)

Collapse
 
jingxue profile image
Jing Xue

There seems to be a gap between EmployeeService and EmployeeDBPort. Presumably EmployeeService would invoke EmployeeDBPort to communicate with the database?

Collapse
 
smartyansh profile image
Anshul Bansal • Edited

In this case EmployeeService is actually EmployeeServiceAdapter that communicates with the database.
I've named the EmployeeService as the EmployeeServiceAdapter, to let reader know that it act as an Adapter.
Thanks for pointing it out. I'll mention in the article.

Collapse
 
jingxue profile image
Jing Xue

Well, now this call chain looks just like a classic three-layer architecture. In particular the call from the controller to the DBPort (actually an implementation of the DBPort) feels somewhat "inside out". If you look at the stereotypical HA diagram, it gives the impression that all ports are external facing.

Perhaps a follow-up expanding on the differences between HA and the classical layered architecture?

Thread Thread
 
smartyansh profile image
Anshul Bansal

Yes, it looks like a classic three-layer architecture. However, the pattern doesn't talk about the layers. Rather, it talks about ports and adapters.
HA expose interface to consume the inside component through a port and adapter is the middleware software component to interact with a port. Adapter can be in multiple technologies.

Thanks for suggesting a follow up on the comparison between HA and classical layered architecture.

Collapse
 
smartyansh profile image
Anshul Bansal

Good point. We can do that too.