Arquitetura Hexagonal Explicada: como Transformar seu Desenvolvimento de Software
Tags: architecture, systemdesign, beginners, learning
Published: 2024-06-16 22:51:51

<!-- TOC start (generated with https://github.com/derlin/bitdowntoc) -->

- [Introdução](#introdução)
- [Ciclo de um projeto](#ciclo-de-um-projeto)
- [Arquitetura vs Design de Software](#arquitetura-vs-design-de-software)
- [Arquitetura Hexagonal](#arquitetura-hexagonal)
- [Hexagonal vs Clean vs Onion](#hexagonal-vs-clean-vs-onion)

<!-- TOC end -->

# Introdução

De forma geral, entende-se que a função de todo desenvolvedor é “resolver problemas utilizando o software como ferramenta”. Esta frase, apesar de simples, pode ser entendida a partir de duas vertentes: complexidade de negócio, envolvendo as regras para se resolver o problema; e a complexidade técnica, que é a tecnologia integrada ao negócio para fazê-lo funcionar.

Muitas vezes, porém, o que observamos são aplicações tão intrinsecamente integradas de forma que não é mais possível destacar o que é complexidade de negócio e o que é a complexidade técnica.

Considere o seguinte cenário: a Empresa X deseja rastrear eventos de usuários em seu aplicativo, como 'account_created', 'home_viewed', e 'payment_finished', e enviá-los para uma plataforma de CRM, Empresa Y, para construir perfis detalhados e aprimorar estratégias de marketing. Para isso, utilizam o SDK da Empresa Y com o método 'send_event', implementado em cada ponto do sistema onde ocorre um evento. Dois meses depois, com o aumento dos custos pelo uso do sistema da Empresa Y, a Empresa X opta por migrar para a Empresa Z. A substituição do SDK de Y pelo de Z seria um processo simples nesse caso?

Podemos facilmente concluir que não. O código de Y ficou acoplado por todo o sistema, “sujando” a complexidade de negócio. Substituir a sua utilização seria um processo árduo e demorado.

A arquitetura hexagonal, também chamada de “Ports and Adapters”, vem exatamente para impedir que isso aconteça. Nesta linha, podemos exemplificar alguns pontos importantes sobre uma arquitetura bem feita:
- Crescimento sustentável: o software precisa ser escalável e possível de ser melhorado de forma simples, de forma que ao longo do tempo ele pague o seu próprio investimento;
- O Software deve ser desenhado por nós e não pelo framework - devemos mitigar o acoplamento entre complexidade técnica e de negócio;
- Possível de ser substituída sem muitos esforços

Em outras palavras, podemos entender a arquitetura como o futuro de um software. 

# Ciclo de um projeto

Ainda sim, muitas vezes, seja por falta de conhecimento sobre arquitetura, ou mudanças complexas de mercado, vemos um software crescer de uma maneira bem pouco sustentável. A seguir exemplifico o ciclo de vida de muitos projetos hoje em dia.

**<u>Fase 1:</u>**
Nesta fase, o projeto é iniciado. Implementa-se o banco, cadastros, autenticação, servidor, views, controllers, etc.

**<u>Fase 2:</u>**
Adiciona-se regras de negócio no cadastro, implementa-se uma ACL para autorização, adicionam-se logs, criação e consumo de APIs.

**<u>Fase 3:</u>**
Os acessos aumentam muito, escala-se horizontalmente a aplicação (upgrade de hardware), começamos a trabalhar com cache, consumir apis de terceiros e implementar regras de negócio de parceiros.

**<u>Fase 4:</u>**
Mais acessos, mais upgrades, mais consultas ao banco que começam a gerar gargalos, implementa-se uma v2 da API.

**<u>Fase 5:</u>**
O software é escalado verticalmente, fazer uploads em multiplas máquinas (começamos a trabalhar com S3), muita refatoração para executar a escala vertical, criação de autoscaling, pipeline de CI/CD.

**<u>Fase 6:</u>**
Começamos a utilizar graphQL para algumas APIs, começam a aparecer bugs em função da mudança nos formatos, problemas com logs em múltiplas máquinas - começa-se a pensar em algum sistema para centralização, cria-se integração com plataforma de CRM.

**<u>Fase 7:</u>**
Começamos a ter inconsistência dos dados de CRM, a pensar em conteinerizar nossa aplicação - precisa-se repensar o CI/CD, memória, logs, nos livrar de código legado.

**<u>Fase 8:</u>**
Começamos a trabalhar com microsserviços, inicia-se compartilhando bancos, começamos a ter problemas de tracing, lentidão no sistema, custo fica elevado.

**<u>Fase 9:</u>**
Não conseguimos mais trabalhar com containers e vamos para Kubernetes, temos que refazer o CI/CD novamente, começa-se a ter problemas de resiliência, começamos a trabalhar com mensageria sem ter muita experiência e começamos a perder mensagens, começa-se a contratar consultoras de software para auxiliar.

**<u>Fase 10:</u>**
Use a imaginação!

---

A partir da análise dessas fases, podemos ver que um software não se perde do dia pra noite, mas sim um dia de cada vez. O sistema começa a ficar com cara de legado, com débitos técnicos que ninguém quer colocar a mão.
A seguir listo algumas reflexões desse ciclo de vida apresentado:
- Faltou visão de futuro: não podemos ter a inocência de achar que um software sempre será simples/pequeno. Uma hora ele cresce;
- Má definição de limites: não houve uma separação clara entre o que é negócio e o que é framework, gerando um acoplamento e dificultando a troca e adição de componentes;
- Falta de preparação para escalamento: no início tudo está no mesmo servidor - cash, logs, upload. Porém, quando começamos escalar horizontalmente, e depois verticalmente, temos que refazer muita coisa. Logo, temos que pensar e nos preparar para escala desde o dia 1.
- Falta de limites para otimizações frequentes: a todo momento o software vai precisar de novas features com prazos apertados e, se não definirmos um limite aceitável, começaremos a criar e manter cada vez mais débitos técnicos;
- Incapaz de lidar com mudanças bruscas: ex.: empresa trocou a gateway de pagamento, ou de plataforma de CRM → conseguimos alterar facilmente a implementação?

Podemos ver que, se tivermos visão de futuro desde o dia 1, conseguimos criar camadas anti-corrupção para que as mudanças não afetem o negócio e não dificultem o nosso trabalho.

# Arquitetura vs Design de Software

É importante também salientar a diferença entre esses dois conceitos, muitas vezes considerados como a mesma coisa. A seguir segue uma definição que considero bem sucinta e completa, resumida do seguinte artigo: https://eximia.co/quais-sao-as-diferencas-entre-arquitetura-e-design-de-software/

---

_“Atividades relacionadas a arquitetura de software são sempre de design. Entretanto, nem todas as atividades de design são sobre arquitetura. O objetivo primário da arquitetura de software é garantir que os atributos de qualidades, restrições de alto nível e os objetivos do negócio, sejam atendidos pelo sistema. Qualquer decisão de design que não tenha relação com este objetivo não é arquitetural. Todas as decisões de design para um componente que não sejam “visíveis” fora dele, geralmente, também não são.”_

---

Em outras palavras, podemos entender a arquitetura como aquilo faz entregarmos o software com qualidade, eficiência, e garantir que o negócio vai funcionar, algo mais abstrato. O Design seria como vamos fazer isso funcionar, algo mais concreto.

Muitas decisões de design não têm a ver com a arquitetura, porém também podem impactar nas decisões arquiteturais. Segue um exemplo:
- “Todos os sistemas terão que gerar logs e ficarem armazenados em um local” - **visão arquitetural**
- “Para que isso seja possível, vamos fazer o log não ser gravado em arquivo e sim sair pelo terminal” - **visão de design**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ktrnq77voix6v6vxsxxd.png)

# Arquitetura Hexagonal

De acordo com Alistair Cockburn, podemos definir a Arquitetura Hexagonal da seguinte forma:

---

_“Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases.”_

---
**Obs.:** fica a dica do artigo: https://alistair.cockburn.us/hexagonal-architecture/

De forma sucinta, é basicamente criar software isolando a forma que pessoas e sistemas o acessam, e como ele acessa outros sistemas. Podemos entender melhor esse conceito a partir da seguinte imagem:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/62i0w88qlwlmms0afg3p.png)

Observando a imagem, temos o coração de nossa aplicação, o negócio, no centro do hexágono. Para haver a comunicação com objetos externos (tanto cliente quanto servidor abstraídos por seus respectivos adaptadores) criamos interfaces (ports), de forma a impedir que minha aplicação (negócio) fique diretamente interligada ao adaptador e vice-versa.

É importante notar aqui que a forma de hexágono é apenas uma estrutura arbitrária para representar - poderia ser qualquer outro formato. A questão é que não queremos misturar o que é complexidade de negócio com a complexidade técnica. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cvwl2b8ecmcxefvk4jyu.png)

Nesta linha, conseguimos componentizar a integração com os diversos objetos externos: logs, cache, banco, filas de mensagens, cliente (REST, gRPC, GraphQL), facilitando sua adição e substituição por meio dos “Ports” e “Adapters”.

De forma mais prática, precisamos utilizar de um dos conceitos de SOLID - **Princípio da Inversão de Dependência** - para atingir essa clareza:
- Módulos de alto nível (negócio) não dependem de módulos de baixo nível (objetos externos), e sim ambos devem depender de abstrações.
- Abstrações não devem depender de detalhes e vice-versa.

O que fazemos aqui é uma inversão de controle. Por exemplo, se eu instanciar uma classe B dentro de outra classe A diretamente, estarei gerando um acoplamento. O que farei ao invés disso é recebê-la dentro do construtor. Porém, para evitar esse acoplamento entre as duas classes, devo receber uma interface no construtor de A, que representará B indiretamente.

Todos esses conceitos mudam completamente a forma como desenvolvemos software de qualidade.

# Hexagonal vs Clean vs Onion

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a1zbc5pcxjamho2ropva.png)

Por fim, podemos diferenciar esses conceitos muitas vezes confundidos. Na arquitetura hexagonal não há um padrão estabelecido de como o código deve ser organizado, quais camadas ou estruturas de pastas utilizar, diferentemente da Clean e Onion Architecture. Ela apenas define o princípio da separação entre o coração da aplicação e os objetos externos.

Neste meio, não precisamos necessariamente implementar uma estrutura conhecida (Clean/Onion) contanto que sigamos alguma regra de organização seguindo a essência dos conceitos que falamos neste artigo. Mas é claro, implementar estruturas já estudadas e conhecidas com certeza facilita bastante o processo.