A arquitetura hexagonal, também conhecida como arquitetura de ports and adapters, é um padrão arquitetural que busca criar sistemas flexíveis, testáveis e de fácil manutenção. Ela foi introduzida por Alistair Cockburn em 2005 como uma alternativa ao padrão arquitetural em camadas.
A principal ideia por trás da arquitetura hexagonal é separar a lógica de negócio central da aplicação das preocupações externas, como interfaces de usuário, banco de dados, serviços externos e integrações. Isso é feito por meio da divisão em três principais componentes: domínio, adaptadores e interfaces.
- Domínio:
- Adaptadores:
- Adaptadores de entrada:
- Adaptadores de saída:
- Interfaces:
O domínio é o núcleo da aplicação e contém a lógica de negócio e as regras do sistema. Ele representa o cerne da aplicação e é independente de qualquer tecnologia externa. Aqui, as entidades, os serviços e os casos de uso são definidos. Essa camada deve ser pura e não depender de detalhes de implementação externa.
Os adaptadores são responsáveis por conectar o domínio às tecnologias externas. Existem dois tipos principais de adaptadores: os adaptadores de entrada (inbound adapters) e os adaptadores de saída (outbound adapters).
São responsáveis por receber as requisições externas e adaptá-las para o formato compreendido pelo domínio. Eles lidam com as interfaces de usuário, como interfaces web, APIs, CLI (Command Line Interface), entre outros. A função desses adaptadores é converter as requisições externas em chamadas para os casos de uso do domínio.
São responsáveis por fornecer implementações concretas das interfaces externas, como banco de dados, serviços de terceiros, sistemas legados, etc. Eles encapsulam a lógica necessária para persistir dados e realizar integrações externas. Os adaptadores de saída também são responsáveis por converter os resultados do domínio em formatos compreendidos pela tecnologia externa.
As interfaces definem os contratos entre o domínio e o mundo externo. Elas permitem a comunicação entre os adaptadores de entrada e os adaptadores de saída. As interfaces são implementadas por adaptadores concretos e são usadas para conectar a lógica de negócio com os componentes externos.
A arquitetura hexagonal busca manter o domínio livre de dependências externas e detalhes de implementação. Isso permite testar o domínio de forma isolada, sem a necessidade de interfaces externas. Os testes podem ser executados utilizando mocks ou stubs para simular as interfaces externas, garantindo a validação correta da lógica de negócio.
Além disso, a arquitetura hexagonal facilita a evolução e a substituição de componentes externos. Se um banco de dados precisa ser alterado, por exemplo, basta criar um novo adaptador de saída que implemente a mesma interface, mantendo a lógica de negócio inalterada.
Em resumo, a arquitetura hexagonal é uma abordagem que promove a separação clara das responsabilidades dentro de uma aplicação.
Exemplo prático em Java com framework Spring
Vamos supor que estamos construindo uma aplicação de gerenciamento de usuários, onde podemos cadastrar, buscar e excluir usuários. Seguindo a arquitetura hexagonal, teremos três principais pacotes em nossa aplicação:
Pacote do Domínio:
User: Representa a entidade de usuário, contendo seus atributos e métodos relacionados.
UserRepository: Define a interface do repositório de usuários, especificando as operações CRUD necessárias.
UserService: Define a interface do serviço de usuário, com os casos de uso relacionados ao gerenciamento de usuários.
Pacote dos Adaptadores:
UserAdapter: Implementa a interface UserRepository e é responsável por lidar com a persistência de dados no banco de dados. Utilizaremos o Spring Data JPA para simplificar a implementação do repositório.
UserController: Implementa as interfaces de entrada, recebendo as requisições externas através de endpoints REST. Ele é responsável por receber as requisições, adaptá-las para o formato adequado e invocar os casos de uso correspondentes no UserService.
Pacote das Interfaces:
UserInputBoundary: Define a interface para os casos de uso do UserService, representando as ações que podem ser realizadas na entidade de usuário.
UserOutputBoundary: Define a interface para os retornos dos casos de uso do UserService, especificando as informações que serão devolvidas como resposta.
Dentro do pacote do Domínio, teremos as seguintes classes:
User.java
public class User {
private String id;
private String name;
// Outros atributos e métodos relevantes
}
UserRepository.java
public interface UserRepository {
User save(User user);
User findById(String id);
void delete(String id);
}
UserService.java
public interface UserService {
User createUser(User user);
User getUserById(String id);
void deleteUser(String id);
}
No pacote dos Adaptadores, teremos:
UserAdapter.java
@Repository
public class UserAdapter implements UserRepository {
private final UserRepositoryImpl userRepositoryImpl;
// Construtor
@Override
public User save(User user) {
return userRepositoryImpl.save(user);
}
@Override
public User findById(String id) {
return userRepositoryImpl.findById(id);
}
@Override
public void delete(String id) {
userRepositoryImpl.deleteById(id);
}
}
UserController.java
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
// Construtor
@PostMapping
public UserDTO createUser(@RequestBody UserDTO userDTO) {
User user = UserMapper.toEntity(userDTO);
User createdUser = userService.createUser(user);
return UserMapper.toDTO(createdUser);
}
@GetMapping("/{id}")
public UserDTO getUserById(@PathVariable String id) {
User user = userService.getUserById(id);
return UserMapper.toDTO(user);
}
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable String id) {
userService.deleteUser(id);
}
}
Por fim, no pacote das Interfaces, teremos:
UserInputBoundary.java
public interface UserInputBoundary {
User createUser(User user);
User getUserById(String id);
void deleteUser(String id);
}
UserOutputBoundary.java
public interface UserOutputBoundary {
UserDTO createUser(UserDTO userDTO);
UserDTO getUserById(String id);
void deleteUser(String id);
}
Essa é apenas uma estrutura básica da implementação da arquitetura hexagonal em uma aplicação Java usando o Spring. É importante ressaltar que existem outras camadas e componentes que podem ser adicionados para uma aplicação mais completa, como validações, mapeamentos, tratamento de exceções, entre outros.
Além disso, a configuração do Spring, como a injeção de dependências e a definição das interfaces de entrada e saída, também são pontos importantes a serem considerados, mas que não foram abordados aqui por questões de simplicidade.
Lembrando que a arquitetura hexagonal oferece a flexibilidade de adaptar as interfaces externas e componentes de persistência sem afetar a lógica de negócio, facilitando a manutenção e os testes automatizados.
Esse exemplo serve como ponto de partida para a implementação de uma arquitetura hexagonal em sua aplicação Java usando o Spring. É sempre importante adaptar e ajustar de acordo com as necessidades do seu projeto específico.
Feito!
Nenhum comentário:
Postar um comentário