Categorias
Software Architecture

Aplicando a Inversão de Dependências pra valer

Você provavelmente já conhece o conceito da Inversão de Dependências, e talvez já aplique usando DI. Mas o buraco é mais embaixo. Descubra porque.

O Princípio da Inversão de Dependências nos ajuda a promover o desacoplamento em nossos sistemas, e uma das formas de se obter isso é transformando algumas classes em interfaces e usando Injeção de Dependência, Service Locator, Factory ou algum outro padrão similar, certo?

Pois bem. Eu acredito que você já conheça ou pelo menos já tenha ouvido falar desses conceitos e técnicas. Mas talvez ainda não tenha encontrado o real sentido ou esteja procurando algo mais. Se este é o seu caso, então este artigo é pra você.

Um pouco de presunção

Eu me arrisco a dizer que muitos desenvolvedores, mesmo alguns mais experientes, ainda não sacaram o que este princípio realmente quer dizer.

Primeiro, porque Inversão de Dependências não é a mesma coisa que Injeção de Dependências. Segundo, porque apesar da Injeção de Dependências ser uma das formas técnicas de se obter a inversão, ainda é possível (e muito fácil) aplicar a Injeção de Dependências e mesmo assim continuar com o problema do acoplamento, ou seja, com as dependências não invertidas.

Quer ver?

Dependências invertidas… ou será mesmo?

Contemple este código e pense por um minuto: quantas vezes você já se deparou com interfaces como esta, bem genéricas, nos sistemas em que trabalhou?

public interface IGenericRepository<T>
{
    void Insert(T entity);
    void Update(T entity);
    void Delete(T entity);
    T Select(object key);
    IQueryable<T> SelectDoMal();
}

Considerando um sistema que supostamente aplica a Inversão de Dependências e cria essa interface para injetar em diversos serviços de regras de negócio, eu pergunto: você acha válido esse tipo de interface?

Se a sua resposta é sim, você ainda não entendeu a inversão de dependências, e por mais que você esteja usando a técnica de Injeção de Dependências, as suas dependências ainda estão na direção errada.

Com essa abordagem, as regras de negócio estão 100% dependentes do banco de dados. Alguns motivos:

  • Nem toda entidade será excluída. O próprio carrinho de compras, na maioria dos e-commerce’s, será apenas marcado como concluído, ou como abandonado, e continuará existindo para fins de análise e relatório pelos gestores do negócio, bem como dará base para campanhas de marketing e outras ações de negócio.
  • Nem toda entidade será atualizada. Em alguns contextos, as entidades são apenas inseridas, e nunca são atualizadas nem excluídas. É o caso de alguns registros de transações, por exemplo as bancárias.
  • Nem toda entidade será lida individualmente. Alguns tipos de dados são inseridos apenas para serem agregados mais tarde e consultados em grupo, como é o caso de contadores em geral (page views, cliques, outros).

Uma interface dessas não modela os métodos de persistência conforme as necessidades de negócio. Pior ainda, abre brecha para que o novo programador do time, desavisado, invoque qualquer um dos métodos que ele não deveria usar, introduzindo bugs no sistema.

Aí você me pergunta: “Mas Phillippe, eu estou desenvolvendo um software: não é natural que o negócio dependa do meu banco de dados ou das minhas APIs?” Não 🙂 Jamais.

Dependências diretas

Pensando na interface genérica de repositório acima, mais que uma dependência técnica entre classes, existe uma dependência técnica entre a regra de negócio e o banco de dados.

É importante tentar não pensar como programador por um momento para entender isto aqui: será que faz sentido que um carrinho de compras dependa de um banco de dados? Não!

Na maioria dos casos, não faz o menor sentido que qualquer regra de negócio dependa de um banco de dados, pois a regra de negócio representa um objetivo que uma empresa ou organização quer atingir, e o banco de dados é um detalhe técnico necessário apenas quando se está aplicando software para resolver o problema.

É só pensar em como este processo aconteceria fora do software: o cliente entra no mercado, coloca produtos no carrinho, passa no caixa para pagar e vai embora com seus produtos. A palavra “banco de dados” nem aparece.

Obviamente, apareceriam equivalências do tipo “registro de produtos”, “catálogo” ou “livro”, mas estes são conceitos genéricos, usados apenas para mostrar que a informação é “lembrada” por alguém ou fica em algum lugar (qualquer lugar), e que sua forma não importa (ou importa muito pouco) para o processo de negócio em si.

Num dia sem energia, o mercado poderia continuar funcionando. Talvez aceitando somente pagamentos em dinheiro, ou anotando numa caderneta para clientes mais fiéis… O ponto aqui é que o mercado não existe por causa da tecnologia; é a tecnologia que evoluiu para melhorar processos que já funcionavam antes.

Então, a não ser que o software que você está desenvolvendo seja algo como uma ferramenta para gestão de banco de dados, o banco de dados é algo que deveria ser deixado de lado enquanto pensamos nos assuntos de negócio que estamos resolvendo. Esses conceitos pertencem a planetas diferentes:

  • Planeta 1: pessoas, carrinho de compras, carro, animais, empresas, negócios
  • Planeta 2: banco de dados, API, formulários de sistemas

É importante notar que, apesar de eu ter escolhido o banco de dados como bode expiatório, o mesmo vale para qualquer conceito que seja de infraestrutura. É que o banco de dados é o mais comum, mas pense também em APIs, sistemas de importação e exportação, envio de emails, etc.

Pensando diferente

Ao escrever um software, o foco da implementação deve estar no problema de negócio que está sendo abordado, não nos detalhes que só pertencem ao software e que não apareceriam no negócio se o problema estivesse sendo abordado sem envolver a programação.

Os detalhes precisam existir, mas eles têm seu lugar, e este lugar é longe dos objetos de domínio.

Vamos à definição do princípio. Ele diz que:

Módulos de nível mais alto não devem depender de módulos de nível mais baixo; ambos devem depender de abstrações.

Módulos de alto nível são as entidades de domínio e as classes de regra de negócio. Os módulos de baixo nível são os componentes de infraestrutura: conexão com banco de dados, acesso de api, leitura de arquivos, entre outros.

Antes de inverter as dependências, vamos a mais uma analogia:

Uma empresa sem clientes não existe (ou não faz sentido que exista), certo? Ao invés do cliente depender da empresa, é a empresa que depende do cliente. Sendo assim, a empresa modela os serviços que ela fornece de acordo com uma necessidade de mercado que ela identificou.

A inversão de dependências é como pensar que o banco de dados também não precisa existir se não houver uma classe de negócios que ele precise atender. Os métodos que essa classe de banco de dados expõe são definidos em função das necessidades da classe de negócio.

Aplicando a Inversão de Dependências

Ao invés de fazer o carrinho de compras depender do banco de dados, vamos fazer o banco de dados depender do carrinho de compras 😉

Isso significa que, ao modelar as classes e camadas deste ecommerce, você não começa pelas interfaces de banco de dados e os métodos de CRUD que elas vão expor. Você começa pelas classes de negócio (produto, carrinho, pedido, comprador), sempre pensando em quais operações do negócio elas modelam, e não somente em quais dados elas armazenam, pois isso seria colocar novamente o banco de dados em primeiro lugar.

Você é uma pessoa do bem, e portanto vai começar escrevendo testes unitários que levam os nomes dos casos de uso do negócio. Daí vai criando as entidades de domínio, combinando dados e comportamento (campos e métodos), para que você não tenha entidades anêmicas. Quando houver regras que não possam ser atendidas dentro de uma entidade de domínio, aí você vai começar a definir os serviços de domínio.

Eventualmente você vai chegar na parte de falar de banco de dados. Mas, até lá, você já tem boa parte do seu sistema “funcionando” com base em testes unitários, entidades, serviços e eventos, sem qualquer menção a qualquer conceito de infraestrutura. A partir daí é que as necessidades específicas de infraestrutura começam a aparecer, e você vai definir as interfaces especificamente de acordo com cada necessidade.

Abstractions should not depend on details. Details should depend upon abstractions.

Conclusão

A inversão de dependências de verdade repensa vários conceitos que vemos todos os dias nas bases de código por aí.

Este princípio é um daqueles que, quando realmente entendido e aplicado, te ajuda a desenvolver softwares que resolvem problemas de negócio, ao invés de adaptar negócios à forma como os programadores pensam.

Bons programadores entendem o negócio e questionam a eficiência dos processos que recebem detalhados por escrito, “prontos” para transformar em código.

Ao fazer isso, eles começam a transformar positivamente o negócio, influenciando na remodelagem de processos, gerando mais eficiência, agregando mais valor lá na ponta.

E só então eles transformam isso em código, mas um código que outras pessoas conseguem ler, entender e manter com mais facilidade.

Por Phillippe Santana

Apaixonado por escrever código que as pessoas possam entender. Sou um desenvolvedor de software, gerente de projetos, empreendedor e entusiasta de pessoas/cultura. Me adicione no [Linkedin](https://www.linkedin.com/in/phillippesantana/) e no [Medium](https://medium.com/@phillippesantana).