Arquitetura hexagonal é um estilo que fala sobre camadas de seus objetos de uma forma que isola sua lógica central de elementos externos. A lógica principal é a parte específica do seu negócio, e os elementos externos são como pontos de integração, por exemplo, bancos de dados, APIs externas, interfaces de usuário e outros. Ele divide o software nas partes interna e externa. As partes internas contêm a lógica do Core Business e a camada Domain (explicada em Layered Architecture). A parte externa consiste em interface do usuário, banco de dados, mensagens e outras coisas. Partes internas e externas se comunicam entre si por meio de portas e adaptadores.
Benefícios
- O software desenvolvido usando esta arquitetura é independente de canais e pode suportar múltiplos canais
- Fácil de trocar os pontos de integração de entrada e saída
- Testar o software torna-se fácil porque podemos simular pontos de integração facilmente
Implementação em Java
Como explicado acima, a arquitetura hexagonal é mais em torno de portas e adaptadores. Em Java, as interfaces implementam as portas e a classe de implementação funciona como os adaptadores. Então, vamos dar uma olhada em um exemplo simples usando o Spring Boot e ver como esse estilo pode ser aplicado a este framework.
Nesta aplicação, temos a funcionalidade para criar / visualizar detalhes do empregado. A lógica do Core Business está no EmployeeService
e o domínio é umEmployee
. Então, essas serão consideradas partes internas
@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
}
Portanto, agora, esse aplicativo pode expor essa funcionalidade via REST ou Messaging. Assim, criamos o EmployeeControllerAdapter
para expor endpoints REST, que implementa o 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);
}
Como parte da lógica de negócios,EmployeeService
também precisa chamar o banco de dados, que é, novamente, um ponto de integração (parte externa), portanto, criamos EmployeeRepositoryPort,
e EmployeeServiceAdapter
implementamos essa porta.
@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);
}
Então, vemos como EmployeeService
usou a EmployeeUIPort
porta para expor seu serviço e EmployeeRepositoryPort
interagir com o banco de dados. Além disso, EmployeeControllerAdapter
eEmployeeServiceAdapter
ajuda a integrar com APIs REST e DB.
Conclusão
Para resumir, a arquitetura hexagonal é uma abordagem usada para dividir o aplicativo em partes internas e externas . Eles estão conectados através de portas (expostas pelo lado de dentro) e adaptadores (implementados pelo lado de fora). Portanto, ao aplicar essa abordagem, o código do caso de uso principal permanece intacto e pode servir a vários canais, suportando diferentes protocolos. Também ajuda a tornar o aplicativo testado com facilidade. No entanto, sugiro não implementar essa arquitetura completamente para todo o aplicativo, mas usar interfaces e adaptadores seletivamente.
O código de todos os exemplos acima pode ser encontrado no GitHub .