Desvendando a Arquitetura Hexagonal

Lucas Trindade
Tableless
Published in
5 min readApr 10, 2019

Estudando Domain Driven Design, através do livro do Vaughn Vernon, fui apresentado para a Arquitetura Hexagonal, e gostei tanto do assunto que hoje estou aqui escrevendo um artigo sobre.

Antes de mais nada, para um bom entendimento do objetivo da arquitetura e forma de implementação da mesma, existem dois assuntos que julgo importante possuir conhecimento prévio: o significado de Domínio e o Princípio da Inversão de Dependência (isso mesmo, o D do SOLID).

Domínio

Domínio nada mais é que a forma para se referir a toda lógica do negócio, sejam elas regras ou processos.

Princípio da Inversão de Dependência (ou Inversão de Controle)

Basicamente, este princípio diz que devemos depender de abstrações, e não de implementações. Na prática, isso significa que, no contexto da orientação a objetos, devemos fazer com que nossa classe utilize interfaces para receber suas dependências, e não classes concretas.

Atenção: não confundir INVERSÃO de dependência com INJEÇÃO de dependência. Apesar do primeiro, dependendo da tecnologia, fazer uso do segundo, não são as mesmas coisas.

Para elucidar, vou expor uma situação onde o princípio será aplicado.

Imagine que possuímos uma classe Order, e a mesma interage com o banco de dados MySQL, seja para consulta ou armazenamento de informações.

O código acima viola o princípio por dois motivos:
1 — A classe Order está acoplada com a classe MysqlAdapter. Se, por algum motivo, tentássemos reaproveitar Order em outro contexto/sistema, obrigatoriamente teríamos que levar MysqlAdapter junto, caso contrário o código geraria um erro.
2 — Order está fazendo uso de uma implementação do Adapter de banco de dados, no nosso caso, o MySQL, quando na verdade deveria trabalhar com uma interface.

Abaixo um exemplo do mesmo código refatorado:

A classe Order agora espera receber uma classe que implemente a AdapterInterface (abstração), e não mais a classe MysqlAdapter (implementação). Ficará a cargo de quem utilizará Order fornecer o adapter para o banco de dados do projeto. Além do desacoplamento, a classe Order se tornou agnóstica ao SGBD que o projeto utiliza.

Arquitetura Hexagonal

Também chamada de Ports and Adapters, a arquitetura hexagonal é uma forma de organizar o código em camadas, cada qual com a sua responsabilidade, tendo como objetivo isolar totalmente a lógica da aplicação do mundo externo. Este isolamento é feito por meio de Portas e Adaptadores (daí o nome Ports and Adapters), onde as Portas são as interfaces que as camadas de baixo nível expõe, e Adaptadores as implementações para as interfaces em questão (alô inversão de dependência).

O isolamento da aplicação vale tanto para entrada de dados quanto saída. Isso significa que o código deve ser agnóstico à forma de acesso, e também ao(s) mecanismo(s) de persistência, envio de notificações, etc.

Imagine o seguinte cenário: um sistema desenvolvido sob o protocolo HTTP cresceu, e demanda agora a utilização de um mecanismo de mensageria, RabbitMQ por exemplo, que utiliza o protocolo AMQP, para executar alguns processos já existentes, ou seja, reaproveitamento de código. Foi também modificado o SGBD para um NoSQL em algumas áreas. Quão custoso seria implementar essas modificações em um sistema que não foi planejado para suprir a necessidade de mudança de tecnologias?

Segundo Roger S. Pressman, em seu livro Engenharia de Software, a manutenção de software pode ser responsável por mais de 70% de todo o esforço despendido por uma organização de Software. E essa porcentagem continua aumentando à medida que mais Software é produzido.

Para Martin Fowler, um sistema mal projetado normalmente precisa de mais código para fazer as mesmas coisas, muitas vezes porque o mesmo código é replicado em diversos lugares diferentes. Assim, um aspecto importante na melhoria do projeto é a eliminação de código duplicado. A importância disto recai sobre futuras modificações no código.

Na figura 1 é possível visualizar a lógica de negócio (Domínio) isolado de fatores externos, como bibliotecas de notificação por SMS e E-mail, banco de dados e ferramenta de busca, e utilizando diferentes formas de entrada de dados, como API (HTTP) e comandos de console (CLI). A forma como o domínio é acessado se dá por meio de Portas e Adaptadores.

Figura 1. Sistema organizado com a arquitetura hexagonal

A arquitetura na prática

Para fixação de um novo conceito, nada melhor que exemplos práticos de utilização do mesmo.

Vamos considerar um sistema de pedidos, onde a aplicação recebe a requisição via HTTP, e armazena a mesma em um banco de dados através do ORM Doctrine.
Conforme explicado no decorrer deste artigo, a arquitetura hexagonal se utiliza de Portas e Adaptadores tanto para a entrada de dados (forma de consumo da aplicação) quanto para saída (persistência das informações, notificações, etc).

Entrada de Dados
As requisições são recebidas no controller OrderController, e para que elas caiam neste controller, é necessário que configuremos corretamente nossas rotas. Estas configurações estão em routes.php. Usarei da sintaxe do microframework Slim por ser simples de entender, mesmo sem um conhecimento prévio da ferramenta.

Neste exemplo, podemos entender como Porta o próprio protocolo HTTP, e a configuração das rotas, juntamente com o objeto Request do Slim, como uma parte significativa do Adaptador, interpretando os parâmetros da requisição (sejam via path, sejam via body) e passando corretamente para a instância de OrderService, que é injetada e utilizada nesta solicitação (totalmente agnóstica à forma como estão sendo recuperadas as informações).

Se a requisição utilizasse um protocolo diferente, a forma de entender e recuperar as informações seriam diferentes, porém OrderService seria utilizada da mesma forma.

Saída
E o outro lado da aplicação? Considere como Porta uma interface de OrderRepository fornecida pela camada de domínio, e Adaptador a implementação da mesma, feita na camada de infraestrutura.

Porta: interface de OrderRepository
Adaptador: implementação de OrderRepository com Doctrine

No exemplo acima, a camada de domínio, através da interface, expõe os métodos que uma implementação deve possuir, porém não importa como essa implementação é feita, desde que, ao final, atenda as necessidades do consumidor.
Utilizando como exemplo o método fromId, na linha 16 de OrderRepository: é importante que um identificador do tipo int seja enviado para o método, e uma instância da entidade Order seja devolvida. A forma como isso acontece não é relevante para o domínio, e deve ficar a cargo da infraestrutura do sistema. No nosso caso, utilizamos o Doctrine, que possui seu mapeamento de classe e métodos de manipulação de banco de dados, porém poderíamos ter utilizado um RedisOrderRepository, e consultado as informações deste banco in-memory, criando a instância de Order de outra forma.
A abordagem de Portas e Adaptadores facilita uma troca futura de tecnologia, já que poderíamos fazer uso de um Service Provider para sempre devolver uma instância de DoctrineOrderRepository quando injetada a interface OrderRepository, e em uma eventual troca de tecnologia, só o Service Provider precisaria ser atualizado.

Quando não usar

Como vimos, a arquitetura hexagonal possui inúmeras vantagens, porém não são em todos os casos que realmente valerá a pena sua utilização.
Em sistemas muito pequenos ou que dificilmente irá gerar trabalho de manutenção/novas features talvez não seja tão interessante o custo-benefício de sua implementação, já que demanda um alto grau de esforço de desenvolvimento.
Como em todos os casos no nosso cotidiano, deve ser feita uma análise de viabilidade, já que podem haver formas mais eficazes de resolver o mesmo problema.

Os códigos deste artigo estão disponíveis no GitHub.
Obrigado, e até a próxima!

--

--