Acoplamento aferente e eferente
O acoplamento “nasce” de diversas formas, mas assume apenas duas naturezas genéricas distintas: aferente e eferente.
O acoplamento aferente de um artefato – seja ele um componente, classe, função, etc – surge quando este é referenciado por outro. Quando, por exemplo, o código de uma classe “A” faz referência ao código de uma classe “B”, diz-se que “B” tem um incremento em seu acoplamento aferente. Por outro lado, o acoplamento eferente de um artefato surge quando este demanda (ou depende) de outro.
Artefatos com acoplamento eferente mais alto “quebram” com mais frequência, pois, além de sua qualidade interna, são fortemente impactados pela qualidade interna de suas dependências.
Artefatos com acoplamento aferente alto são potenciais “pontos de falha” graves. Afinal de contas, problemas em sua qualidade interna afetam todos os que dele dependem.
Um indicador derivado das métricas de acoplamentos aferente e eferente é a instabilidade. Trata-se da proporção, em um determinado artefato, de acoplamento eferente em relação ao acoplamento total.
Quanto maior a instabilidade ponderada de um conjunto de artefatos, em um determinado módulo, maiores são as chances de “quebra”.
O “problema” do banco de dados monolítico
O banco de dados é componente importante na maioria das aplicações.
Tradicionalmente, grandes aplicações concentram as informações de todos os seus módulos em um mesmo banco de dados. Uma das justificativas é que isso simplifica a integração entre estes módulos. O “problema”, entretanto, é que junto com a potencial simplicidade de integração há o crescimento do acoplamento aferente. Afinal, falhas no banco de dados são cada vez mais impactantes na medida em que mais “módulos” compartilham dele.
A eventualidade de decomposição de um sistema em serviços, ou microsserviços, preservando o banco de dados monolítico potencializa o problema.
Combatendo acoplamento com abstrações
Um dos principais recursos para a redução o acoplamento aferente, pelo menos do ponto de vista estático, é a introdução de abstrações. Ou seja, a substituição das dependências de implementações concretas por abstratas, como interfaces e/ou contratos.
As abstrações tendem a se tornar “difíceis de mudar” na medida em que o software vai sofrendo adaptações, com a criação de mais implementações concretas.
Entretanto, bem mais comum (e bem menos controlada) é a proliferação de artefatos “consumidores”, operando através de inversão de controle ou mecanismos de descoberta.
Essa “proliferação descontrolada”, aliás, é a justificativa para que aprimoramentos sejam explicitados em abstrações novas, de maneira a não quebrar contratos mais antigos e potencialmente não evidentes.
O volume de abstração também é uma métrica arquitetural interessante. De maneira simples, trata-se da proporção, em um determinado escopo, da quantidade de abstrações com relação a quantidade de implementações concretas.
O “lado ruim” da testabilidade
Artefatos instáveis, ainda em desenvolvimento, podem demandar atualizações nas abstrações que os representam que, como sabemos, tem complexidade aferente sempre maior. Por isso, com frequência é justificável postergar a inclusão de abstrações, mesmo sob pena de reduzir a testabilidade.
O “lado ruim” de IRepository
Uma prática comum em implementações baseadas em Domain-driven Design é a separação entre contrato (interface pública) para repositórios, geralmente colocados no mesmo pacote destinado aos objetos que formam o modelo do domínio, das implementações concretas. A justificativa mais comum é dar “liberdade” para a eventualidade de trocar a tecnologia de persistência. Outra justificativa, é facilitar a escrita de testes automatizados.
O “lado ruim” das APIs públicas
APIs públicas tem acoplamento aferente dinâmico difícil de medir. Entretanto, como se sabe, quanto mais bem sucedidas, geralmente maior será tal acoplamento e, consequentemente, maiores serão as dificuldades para evoluir. Essa é a razão principal para o versionamento eficiente dos contratos públicos, com políticas claras para descontinuidades.
O “lado ruim” das arquiteturas event-driven
Outro “antídoto” comum para a complexidade aferente é adoção de comunicação através de eventos. Aliás, essa é uma “bomba” contra o acoplamento que, como tal, acaba tendo efeitos colaterais nem sempre desejáveis.
Sistemas baseados em eventos demandam o planejamento de coreografias, regulando o comportamento dinâmico, tremendamente difíceis de entender sem o suporte de documentação eficiente.
A alternativa é a introdução de componentes especificamente dedicados a orquestração, porém, estes, acabam comprometidos por terem alto acoplamento aferente.
Determinando o volume ideal de abstrações
O volume ideal de abstrações tem relação direta com a instabilidade observada. Quando maior a instabilidade de um artefato, menor a necessidade de uma abstração que alivie seu acoplamento aferente. Por outro lado, artefatos com baixa instabilidade “gritam” por abstrações.
No gráfico indicado acima, artefatos que se posicionem no canto superior-direito se encaixam na zone of uselessness, ou seja, código tão abstrato que se converte em algo difícil de manter. No outro extremo, artefatos que se posicionem no canto inferior esquerdo estão na zone of pain, ou seja, acoplamento aferente extremamente elevado sem o “alívio” de abstrações que habilitem testabilidade e novas implementações.
A medida de desequilíbrio entre a instabilidade e o volume de abstrações de um software pode ser determinada pela distância da condição atual e a proporção adequada.
Modularidade
Um módulo é, por definição, um agrupamento de unidades independentes que pode ser utilizado para a composição de estruturas mais complexas.
No dia-a-dia, os desenvolvedores têm diferentes recursos para expressar modularidade. Começando pelo nível básico, em métodos e funções, até níveis mais altos, como classes, pacotes ou namespaces e assemblies – todas essas opções com diferentes práticas e políticas para regras de escopo e visibilidade. Quanto mais frágeis as “fronteiras” de cada módulo, maiores são as dificuldades para o evolvability.
O tamanho certo para os módulos de um sistema de software extrapolam características técnicas e tem relação direta com o nível de maturidade das organizações que os mantém.
A coesão do módulo é determinada pela clara ligação entre os artefatos que o constituem. Quanto menor a coesão, maiores as chances de erosão.
A modularização é um recurso fundamental para a gestão do acoplamento.
O “lado ruim” de microsserviços
A fragmentação de microsserviços é, em essência, uma estratégia para definição da modularidade de um sistema de software, mas não é a única!
Sob a perspectiva da gestão do acoplamento, microsserviços agrupam uma série de artefatos – incluindo código, banco de dados, mecanismos de replicação, etc – localizando o acoplamento entre eles, tentando preservar a coesão. Além disso, têm suas funcionalidades expostas de maneira controlada e bem versionada.
Para toda jornada é necessário ter o nível adequado de “bagagem”
Acoplamento é inevitável mas, sem os cuidados adequados, pode atingir níveis insustentáveis. Criar “pontos de acoplamento” é fácil e, depois disso, remover é difícil.
Arquiteturas de software de qualidade são, acima de tudo, aquelas que evoluem mitigando necessidades para o acoplamento. Além disso, é parte das práticas de arquitetura de software monitorar e estabelecer estratégias para lidar com o acoplamento quando ele surgir.
// TODO
Antes de avançar para o próximo capítulo, recomendo as seguintes reflexões:
- A estratégia de modularidade do software em que você está trabalhando colabora para a mitigação de causas para acoplamento?
- Os testes automatizados estão “exigindo” abstrações de maneira razoável?
- Que artefatos de sua solução tem alto nível de acoplamento aferente?
- Os artefatos com acoplamento aferente tem níveis adequados de testes automatizados?
- As abstrações com acoplamento aferente mais elevado tem estratégia clara de versionamento?