Design de software
A estrutura que reflete o negócio: modularidade, coesão e acoplamento
Por que modularizar apenas por afinidade técnica, se os elementos de negócio são os verdadeiros protagonistas da aplicação?
Cristiano Rego Barros
6 min de leitura
Publicado originalmente no Medium
Nos dias atuais, em que conceitos como DDD (Domain-Driven Design), microsserviços e Clean Architecture estão cada vez mais em alta, muito se fala em modularidade. No entanto, surgem algumas dúvidas ao seu redor, como:
- O que realmente significa a modularidade?
- A modularidade é apenas técnica?
- Como posso construir uma aplicação modular?
Essas são questões que muitas vezes não são explicadas, e é preciso buscar esse entendimento para compreender como alcançá-la. Como já dizia Glenford Myers em 1978:
“Noventa e cinco por cento das palavras [sobre arquitetura de software] são usadas enaltecendo os benefícios da ‘modularidade’ e poucas, se algumas, tratam de como alcançá-la.”
— Glenford J. Myers (1978)
Então, para ajudar nesse assunto, busco explicar um pouco sobre o tema neste breve artigo.
O que é modularidade
O dicionário define modularidade como a qualidade daquilo que é modular ou constituído por módulos. Já Eric Evans, autor de Domain-Driven Design, afirma que módulos são elementos de design consolidados, nos quais deve existir baixo acoplamento entre si e alta coesão interna.
Coesão
A coesão pode ser entendida como o nível de relacionamento entre os elementos internos de um módulo. Ou seja, um módulo coeso agrupa componentes que fazem sentido estarem juntos, pois colaboram para o mesmo objetivo ou responsabilidade. Quanto mais relacionados esses componentes são entre si, mais coeso é o módulo.
Na imagem abaixo, temos um grafo ilustrativo com três agrupamentos:
- Nuvem: elementos 1 a 5;
- Estrela: elementos 6 a 8;
- Elemento 9 isolado.
Poderiam se pensar diferentes formas de agrupar ou modularizar os nós do grafo acima, como por exemplo:
- Nós pares e ímpares;
- Nós com relacionamento e sem relacionamento, etc.
No entanto, por não haver nenhum contexto descrito, pode-se afirmar que, de forma visual, a divisão citada anteriormente seria a que gera módulos com itens mais conectados, ou seja, o formato mais coeso.
Essa busca pelos melhores motivos (ou melhores características) para agrupar elementos do design é o que traz valor aos seus módulos para que sejam coesos.
Acoplamento
Já o acoplamento pode ser caracterizado como o nível de dependência entre diferentes itens.
Então, falar de elementos com alto acoplamento entre si pode ser traduzido como uma grande dependência entre eles. Entende-se, assim, que elementos com baixo acoplamento são elementos em que a independência é promovida.
Ao analisar a ilustração abaixo, pode-se notar que os elementos do Módulo A não têm nenhuma relação ou dependência com elementos dos demais módulos. Por outro lado, os itens em B estão em frequente relação e dependência com o elemento em C, promovendo assim um acoplamento entre módulos.
Essas frequentes associações podem fazer com que os itens em B se tornem dependentes e até reféns de mudanças causadas pelos itens de C. Nesse caso, os módulos começam a se tornar frágeis, visto que não estão suprindo suas próprias necessidades. Por isso, busca-se módulos desacoplados e independentes, para que sejam módulos fortes.
Considerando um módulo coeso, o acoplamento de seus itens se torna mais aceitável, pois esses elementos estarão conectados e trabalharão em torno de um mesmo objetivo — ou seja, mudanças em peças desse módulo muito provavelmente serão válidas para as demais.
Por outro lado, ao se acoplar a itens em módulos não coesos ou entre diferentes módulos, as mudanças em um item muito provavelmente não serão relevantes aos demais, podendo gerar problemas nas relações. Posto isso, pode-se entender a importância da busca por módulos coesos e desacoplados e, da mesma forma, a necessidade de fugir de módulos acoplados e desconexos.
Modularidade em camadas
Como afirmado por Richards e Ford:
“Sistemas de software modelam sistemas complexos, que levam à entropia (ou desordem).”
— Mark Richards e Neal Ford
Partindo desse princípio, ressalta-se a importância de buscar sempre uma organização clara e coesa em sistemas de software, para que se atinja um projeto manutenível e evolutivo — ao contrário da tendência casual, em que o arranjo da base de código atrapalha a evolução do sistema.
Geralmente, em softwares modernos, o comum é dividir componentes e artefatos orientados pela diferenciação técnica entre eles. Dessa forma surge a arquitetura em camadas, em que os componentes internos de um software são organizados de acordo com sua responsabilidade técnica, criando camadas como:
- Camada de apresentação (controllers, consumidores);
- Camada de aplicação (casos de uso, classes de serviço);
- Camada de infraestrutura (mapeamento de banco de dados, gerenciamento de conexões com drivers externos).
No entanto, será que esses grupos de componentes (ou módulos) estão realmente coesos e desacoplados? Será que esse formato de divisão é o bastante para termos uma base de código limpa e clara?
À primeira vista, essa divisão parece fazer sentido, já que os elementos compartilham uma responsabilidade técnica comum. No entanto, ao adotar uma divisão em camadas, é comum cair no cenário representado pela imagem abaixo, em que cada camada possui diferentes elementos que executam as mesmas tarefas, porém para diferentes domínios.
Imagine que haja limites rígidos entre cada domínio, em que os elementos de um domínio de negócio não conversem com elementos de outro, relacionando-se apenas com itens do seu mesmo contexto. Os relacionamentos entre elementos e componentes na aplicação se apresentarão de forma semelhante à da imagem abaixo:
Dessa forma, pode-se ver que há um grande volume de comunicação entre módulos e nenhuma dentro dos módulos. Isso acontece porque elementos executores de determinada tarefa não se relacionam (ou não devem se relacionar) com outros do mesmo tipo: um lápis não se relaciona com outro lápis, mas sim com um papel, ou com a mão de quem escreve. Suas relações são definidas por seu objetivo de negócio — por exemplo, os elementos para que alguém escreva um livro.
Modularidade por domínio
Como visto no tópico anterior, a forma casual de modularização por camadas técnicas acaba não sendo a melhor escolha, podendo inclusive se tornar uma armadilha em certos contextos. Surge, então, a modularização por domínios de negócio, muito influenciada por conceitos trazidos pelo DDD (Domain-Driven Design).
Basicamente, a ideia por trás dessa forma de modularidade consiste em dividir os componentes da aplicação por suas capacidades de negócio, para que se tenham módulos coesos e desacoplados. Uma representação desse conceito segue a linha da imagem abaixo:
Essa estratégia acaba sendo muito agradável para projetos iniciais que miram a arquitetura de microsserviços. Nessa arquitetura, uma das grandes dificuldades e maiores questionamentos gira em torno da granularidade correta dos serviços. No início do projeto, quando se sabe pouco do domínio, a arquitetura modularizada por domínio pode ser uma excelente alternativa.
O projeto pode começar com poucos ou até mesmo um único serviço, dividido internamente por domínios. À medida que cada um se torna relativamente robusto, o módulo inteiro pode ser retirado e movido para outro serviço, em outra base de código, sem impactar os demais nem ser impactado — pois passa a seguir seu próprio ciclo de vida de forma independente.
Além disso, nada impede que cada módulo de domínio seja organizado internamente por capacidades técnicas. Obtém-se assim uma estrutura híbrida: organizada em camadas, mas modularizada por domínios de negócio — algo semelhante à imagem abaixo:
Essa abordagem não apenas promove coesão e desacoplamento reais, como também abre caminho para uma arquitetura mais alinhada ao negócio. Para se aprofundar nesse tipo de organização modular, recomenda-se o estudo de Domain-Driven Design (DDD), que trata com mais profundidade a identificação e a separação de domínios de forma estratégica.
Conclusão
Em resumo, modularidade vai muito além de dividir código em camadas técnicas. Trata-se de alinhar estrutura e propósito, garantindo que a organização do software reflita a lógica do negócio que ele representa. Adotar uma modularização orientada a domínios é um passo sólido rumo a sistemas mais coesos, escaláveis e com menor custo de manutenção ao longo do tempo.
Se você já enfrentou desafios com modularidade nos seus projetos ou tem experiências diferentes sobre como organizar sua base de código, me conte. Vamos continuar esse debate e trocar aprendizados sobre como tornar nossas aplicações mais sustentáveis e alinhadas ao negócio.