anúncios

sábado, 20 de maio de 2023

Entendendo a Arquitetura Hexagonal

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.

  1. Domínio:
  2. 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.

  3. Adaptadores:
  4. 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).

  5. Adaptadores de entrada:
  6. 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.

  7. Adaptadores de saída:
  8. 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.

  9. Interfaces:
  10. 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