1. Overview
In this tutorial, we'll discuss
Hexagonal Architecture in Java through a working example.
2. Hexagonal Architecture
The core concept of this pattern is to implement the business logic inside the hexagon and expose it outside through the
ports and
adapters. 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 or third party integrations.
In terms of programming (implementation), a port is an interface while an adapter is the concrete implementation of a given port.
3. Hexagonal Architecture example in Java
Let's create a simple Spring Boot REST App and try to register/view Student details using the Hexagonal Architecture.
3.1 Define Domain layer
To start with, we'll create an entity named
Student. Then, we'll add
StudentService class to keep the core business logic to persist\view data. Here, the
Student and
StudentService are part of the inside component. Therefore, we'll create ports to expose them.
public class Student {
private int studentId;
private String name;
private int age;
}
public interface StudentService {
public List<Student> findAll();
public void save(Student student);
}
Let's implement StudentService interface now.
@Service
public class StudentServiceImpl implements StudentService {
private StudentDBPort studentDBPort;
@Autowired
public StudentServiceImpl(StudentDBPort studentDBPort) {
super();
this.studentDBPort = studentDBPort;
}
@Override public List<Student> findAll() {
return studentDBPort.findAll();
}
@Override public void save(Student student) {
studentDBPort.save(student);
}
}
3.2 Declare Ports
Then, we'll create the StudentDBPort interface to communicate with the database:
public interface StudentDBPort {
public List<Student> findAll();
public void save(Student student);
}
3.3 Implement Adapters
Let's create the
StudentControllerAdapter class that act as a RestController. Therefore, it'll act as an adapter to REST client.
public class StudentControllerAdapter implements StudentRESTPort {
@Autowired
private StudentService studentService;
@RequestMapping(method = RequestMethod.POST, value = "/register")
public Student registerStudent(@RequestBody Student student) {
studentService.save(student);
return student;
}
@RequestMapping(method = RequestMethod.GET, value = "/viewAll")
public List<Student> getAllStudents() {
return studentService.findAll();
}
}
Then, we'll create the
StudentServiceDBAdapter class that implements the
StudentDBPort interface. So, it'll behave as an adapter to persist data in the database:
public class StudentServiceDBAdapter implements StudentDBPort {
private EntityManager entityManager;
@Override
public List<Student> findAll() {
Session currentSession = entityManager.unwrap(Session.class);
Query<Student> theQuery = currentSession.createQuery("from Student", Student.class);
List<Student> students = theQuery.getResultList();
return students;
}
@Override
public void save(Student student) {
Session currentSession = entityManager.unwrap(Session.class);
currentSession.saveOrUpdate(student);
}
}
That's it, we've successfully separated our App into the inside and outside components.
4. Benefits
The benefits of this architecture are:
- Independence : The core layer is independent of outer layers
- Testability : Each dependent (outer) component could be easily replaced with a stub implementation so that any component can be tested with ease
- Flexibility : Any number of ports can be added easily to serve multiple functionalities\channels
5. Conclusion
In this article, we've learned how to use Hexagonal Architecture in Java. We've seen the core business logic can be exposed as ports via interfaces and consumed by different adapters through implementation classes.