Hexagonal Architecture for Java
Learn more about how you can isolate your core logic from outside elements.
Join the DZone community and get the full member experience.
Join For FreeHexagonal architecture is a style that talks about layering your objects in a way that isolates your core logic from outside elements. The core logic is the piece specific to your business, and outside elements are like integration points, e.g. DBs, external APIs, UIs, and others. It divides software into the inside and outside parts. Inside parts contain Core Business logic and the Domain layer (explained in LayeredArchitecture). The outside part consists of UI, database, messaging, and other stuff. Inside and Outside parts both communicate with each other via ports and adapters.
Benefits
- Software developed using this architecture is independent of channels and can support multiple channels
- Easy to swap out the inbound and outbound integration points
- Testing the software becomes easy because we can mock integration points easily
Implementation in Java
As explained above, the hexagonal architecture is more around ports and adapters. In Java, interfaces implement the ports and the implementation class works as the adapters. So, we will take a look at a simple example using the Spring Boot application and see how this style can be applied to this app.
In this application, we have the functionality to create/view Employee Details. The Core Business logic is in the EmployeeService
and the domain is an Employee
. So, these will be considered as inside parts.
@Service
public class EmployeeService {
@Autowired
private EmployeeRepositoryPort employeeRepository;
public void create(String name, String role, long salary){
employeeRepository.create(name, role, salary);
}
public Employee view(Integer userId){
return employeeRepository.getEmployee(userId);
}
}
@Entity
@Table(name = "employee")
public class Employee{
@Id
@GeneratedValue
@Column(name = "id")
private Integer id;
@Column(name = "name", nullable = false)
private String name;
@Column(name = "role", nullable = false)
private String role;
@Column(name = "salary", nullable = false)
private long salary;
// Setter and Getter methods
}
So now, this application can expose this functionality via REST or Messaging. Hence, we have created the EmployeeControllerAdapter
to expose REST endpoints, which implements the EmployeeUIPort
.
@RestController
@RequestMapping("/employees/")
public class EmployeeControllerAdapter implements EmployeeUIPort{
@Autowired
private EmployeeService employeeService;
@Override
public void create(@RequestBody Employee request) {
employeeService.create(request.getName(), request.getRole(), request.getSalary());
}
@Override
public Employee view(@PathVariable Integer id) {
Employee employee = employeeService.view(id);
return employee;
}
}
public interface EmployeeUIPort {
@PostMapping("create")
public void create(@RequestBody Employee request);
@GetMapping("view/{id}")
public Employee view(@PathVariable Integer userId);
}
As part of the business logic, EmployeeService
also needs to call the DB, which is, again, an integration point (outside part), so we have created the EmployeeRepositoryPort,
andEmployeeServiceAdapter
implements this port.
@Service
public class EmployeeServiceAdapter implements EmployeeRepositoryPort {
@PersistenceContext
private EntityManager entityManager;
@Transactional
@Override
public void create(String name, String role, long salary) {
Employee employee = new Employee();
employee.setName(name);
employee.setRole(role);
employee.setSalary(salary);
entityManager.persist(employee);
}
@Override
public Employee getEmployee(Integer userId) {
return entityManager.find(Employee.class, userId);
}
}
public interface EmployeeRepositoryPort {
void create(String name, String role, long salary);
Employee getEmployee(Integer userId);
}
So, we see how EmployeeService
has used theEmployeeUIPort
port to expose its service and EmployeeRepositoryPort
to interact with the DB. Also, EmployeeControllerAdapter
and EmployeeServiceAdapter
help to integrate with REST APIs and DB.
Summary
To summarize, the hexagonal architecture is an approach used to divide the application into inside and outside parts. They are connected through ports (exposed by the inside) and adapters (implemented by the outside). So, by applying this approach, the core use case code remains intact and can serve to multiple channels, supporting different protocols. It also helps to make the application tested easily. However, I would suggest not to implement this architecture fully for the whole application but use interfaces and adapters selectively.
As always, the code of all examples above can be found over on GitHub.
Opinions expressed by DZone contributors are their own.
Comments