Arquitetura Hexagonal

Desvendando a Arquitetura de Portas e Adaptadores: Flexibilidade e Adaptabilidade no Desenvolvimento de Software.

Arquitetura Hexagonal

Fundamentos da Arquitetura de Portas e Adaptadores

Principais objetivos: separação de preocupações, flexibilidade e testabilidade.

Componentes Chave da Arquitetura

Núcleo da Aplicação: Descrição do domínio da aplicação e sua lógica de negócios central.

Portas: Explicação de como as portas atuam como pontos de interação para comunicação externa.

Adaptadores: A função dos adaptadores em conectar o núcleo da aplicação a diferentes tecnologias ou sistemas externos.

Benefícios da Implementação

Flexibilidade: Como a arquitetura permite a substituição e integração fácil de novos componentes.

Testabilidade: Facilidade de testes unitários e de integração, dada a clara separação entre a lógica de negócios e a infraestrutura.

Manutenibilidade: Melhorias na manutenção do código graças à modularidade e ao baixo acoplamento.

Implementação

Aqui irei demonstrar uma implementação da arquitetura apenas com um Usecase.

  • Postar um artigo.

Importante informa que os nomes dos pacotes e sua organização não é regra, porém essa é uma boa forma para organizar e estruturar a aplicação para seguir a arquitetura hexagonal.

Ferramentas utilizadas:

  • spring-boot

  • spring-web

  • spring-data

  • gradle

  • lombok

  • model-mapper

  • banco de dados H2

Representação da arquitetura hexagonal

Aqui está uma explicação dos diferentes pacotes e suas responsabilidades:

  1. adapters: Este pacote contém todos os adaptadores da aplicação. Os adaptadores são implementações que adaptam a comunicação da aplicação central para o mundo externo ou vice-versa. Lembrando que os adapters podem ser de entrada e saída.

    • in.rest.v1: - Importante sempre manter o versionamento para organização

      • dto: Objetos de Transferência de Dados usados para trocar informações entre o sistema central e os usuários através de endpoints REST.

        O mapper tem a responsabilidade de realizar o mapeamento DTO - Domain e vice-versa para manter a camada de negócio independente e se preocupar apenas com o domínio.

        @Getter
        @Setter
        public class ArticleDTO {
            private UUID id;
            private String title;
            private String content;
        }
        @Component
        public class ArticleMapper {

            private final ModelMapper modelMapper;

            @Autowired
            public ArticleMapper(ModelMapper modelMapper) {
                this.modelMapper = modelMapper;
            }

            public ArticleDTO toDTO(Article article) {
                return modelMapper.map(article, ArticleDTO.class);
            }

            public Article toDomain(ArticleDTO dto) {
                return modelMapper.map(dto, Article.class);
            }
        }
  • ArticleResource: Um controlador REST no Spring que lida com requisições HTTP para operações relacionadas a artigos (por exemplo, criar, buscar, atualizar, deletar artigos).
        @RestController
        @RequestMapping("/articles")
        public class ArticleResource {


            private final PostArticleInputPort postArticleInputPort;
            private final ArticleMapper articleMapper;

            public ArticleResource(
                                   PostArticleInputPort postArticleInputPort,
                                   ArticleMapper articleMapper) {

                this.postArticleInputPort = postArticleInputPort;
                this.articleMapper = articleMapper;
            }


            @PostMapping
            public ArticleDTO createArticle(@RequestBody ArticleDTO articleDTO) {
                Article article = articleMapper.toDomain(articleDTO);
                Article createdArticle = postArticleInputPort.createArticle(article);
                return articleMapper.toDTO(createdArticle);
            }
        }
  • out.repository.database.jpa:

    • dto: DTOs que podem ser usados especificamente para operações relacionadas ao JPA.

      O mapper aparece aqui novamente, porém dessa vez em isolar a entidade do domínio.

        @Component
        public class ArticleEntityMapper {
      
            private final ModelMapper modelMapper;
      
            @Autowired
            public ArticleEntityMapper(ModelMapper modelMapper) {
                this.modelMapper = modelMapper;
            }
      
            public ArticleEntity toEntity(Article article) {
                return modelMapper.map(article, ArticleEntity.class);
            }
      
            public Article toDomain(ArticleEntity entity) {
                return modelMapper.map(entity, Article.class);
            }
        }
      
    • entity: Entidades JPA que representam o modelo de persistência da aplicação.

        @Getter
        @Setter
        @NoArgsConstructor
        @AllArgsConstructor
        @Entity
        @Table(name = "articles")
        public class ArticleEntity {
      
            @Id
            private UUID id;
            private String title;
            private String content;
            private String author;
        }
      
    • ArticleH2Adapter: Um adaptador para o banco de dados H2, fornecendo a implementação para as operações de persistência definidas pela interface ArticleRepository.

        @Component
        public class ArticleH2Adapter implements PostArticleOutputPort {
            private final ArticleRepository articleRepository;
            private final ArticleEntityMapper articleEntityMapper;

            public ArticleH2Adapter(ArticleRepository articleRepository, ArticleEntityMapper articleEntityMapper) {
                this.articleRepository = articleRepository;
                this.articleEntityMapper = articleEntityMapper;
            }

            @Override
            public Article createArticle(Article article) {
                ArticleEntity entity = articleEntityMapper.toEntity(article);
                ArticleEntity savedEntity = articleRepository.save(entity);
                return articleEntityMapper.toDomain(savedEntity);
            }
  • ArticleRepository: Uma interface do Spring Data que estende JpaRepository
        @Repository
        public interface ArticleRepository extends JpaRepository<ArticleEntity, UUID> {
        }
  1. application: Este pacote contém a lógica específica da aplicação que contempla o negócio.

    • core:

      • domain: modelos de domínio (objetos de negócios) e serviços de domínio.

          @Getter
          @Setter
          @NoArgsConstructor
          @AllArgsConstructor
          public class Article {
        
              private UUID id;
              private String title;
              private String content;
              private String author;
          }
        
      • usecase: regras de negócio específicas da aplicação (casos de uso).

        public class CreateArticleUseCase implements PostArticleInputPort {
            private final PostArticleOutputPort postArticleOutputPort;

            public CreateArticleUseCase(PostArticleOutputPort postArticleOutputPort) {
                this.postArticleOutputPort = postArticleOutputPort;
            }

            @Override
            public Article createArticle(Article article) {
                return postArticleOutputPort.createArticle(article);
            }
        }
  • ports:

    • in: portas de entrada, que definem as interfaces que a lógica central fornece para os adaptadores implementarem.

      Exemplos: REST, graphQL, GRPC, File, PubSub etc

        public interface PostArticleInputPort {

            Article createArticle(Article article);
        }
  • out: portas de saída, que definem as interfaces que a lógica central exige dos adaptadores.

Exemplos: REST, Client , GRPC, File, PubSub, Database etc

  •           public interface PostArticleOutputPort {
    
                  Article createArticle(Article article);
              }
    
  1. config: Este conteria classes de configuração para a aplicação Spring.
    Lembre-se a camada de domínio tem que ser pura, não pode depender de tecnologias ou frameworks, dessa forma temos uma classe BeanConfig que realiza a injeção desses beans necessários no Usecase.

    Aqui podemos ter outras configs como Open API, Certificados SSL e etc.

     @Configuration
     public class BeanConfig {
    
         @Bean
         public ModelMapper modelMapper() {
             return new ModelMapper();
         }
    
         @Bean
         public CreateArticleUseCase createArticleUseCase(PostArticleOutputPort postArticleOutputPort) {
             return new CreateArticleUseCase(postArticleOutputPort);
         }
    
  2. shared-kernel: Este pacote poderia incluir funcionalidades comuns e lógica de domínio compartilhada que pode ser usada em diferentes partes da aplicação ou até mesmo pacotes compartilhados.

  3. HexaArchSpringApplication: Esta é a classe principal da aplicação com o método main que inicia a aplicação Spring.

Esta estrutura é consistente com os princípios da Arquitetura Hexagonal, que se concentra na separação de preocupações isolando a lógica central da aplicação de influências externas e efeitos colaterais. O uso de interfaces (ports) permite que o núcleo da aplicação permaneça agnóstico quanto aos detalhes de acesso a banco de dados, comunicação de serviço externo e detalhes da interface do usuário.

No contexto de um blog, o núcleo da aplicação lidaria com casos de uso como criar um novo post, editar um post, excluir um post, comentar, etc. Os adaptadores então forneceriam as implementações necessárias para interagir com um banco de dados (como H2), servir endpoints RESTful e, potencialmente, interagir com outros sistemas ou APIs

Desafios e Considerações

Análise dos possíveis desafios na adoção da arquitetura, como a curva de aprendizado e complexidade inicial.

Considerações sobre quando e para quais projetos a arquitetura é mais adequada.

Complexidade na implementação devido a verbosidade de código e separação de responsabilidades.

Código completo usando arquitetura hexagonal para um CRUD de artigos.

Fontes:
https://helpdev.com.br/
https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html