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);
}
Then, we'll create the EmployeeDBPort interface to communicate with the database:
public interface EmployeeDBPort {
void register(String name);
Employee getEmployee(Integer id);
}
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;
}
}
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);
}
}
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)
There seems to be a gap between
EmployeeService
andEmployeeDBPort
. PresumablyEmployeeService
would invokeEmployeeDBPort
to communicate with the database?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.
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?
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.
Good point. We can do that too.