Arquitetura Hexagonal
Desvendando a Arquitetura de Portas e Adaptadores: Flexibilidade e Adaptabilidade no Desenvolvimento de Software.
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:
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> {
}
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); }
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); }
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.
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