fbpx
IOC / DI – Você está fazendo isso errado!
Publicado em: quarta-feira, 8 de jan de 2014
Categorias: Arquitetura

Olá, agora vou tocar em um assunto que parece uma ferida para muitos, mas considero bem básico. Infelizmente, acredito haja quem confunda estes conceitos, e por ter encontrado pela estrada alguns projetos com tais problemas relacionados à compreensão de IoC e DI, venho escrever esse post. É um tema tão cotidiano que hoje está disponível também no Macoratti*, portanto não vou explicar o conceito, mas me ater no que vejo de errado por aí.

Service Locators

A questão que é que vejo em muitos lugares, implementações de Service Locators e Factories como sendo consideradas implementações de IoC e/ou DI. Esse é um equívoco gigantesco e causa problemas não só estruturais, como o aumento da complexidade nas soluções, também afeta o design de grandes sistemas. É o tipo de problema que você leva meses para resolver, pois geralmente está proliferado em todo o código e não é difícil encontrarmos outros problemas graves nas modelagens, causados geralmente pela abordagem.

Vamos ao que interessa, vamos falar de algumas soluções que vejo no lugar de IoC / DI.

1) Service Locator é um padrão para resolução de dependências, mas não é um padrão para injeção das dependências.

Quando seu código chama um Service Locator para obter uma dependência, a classe em questão está assumindo o controle da execução, e está apenas delegando para um terceiro (o Service Locator) o papel de encontrar uma instância correta. Se houvesse injeção de dependência, sua classe não precisaria chamar um Service Locator, a dependência já estaria injetada e pronta para uso. Nesse caso, você não inverteu o controle, e sequer injetou a dependência. Isso significa que cada classe que precise de uma dependência, precisa saber construí-la ou solicitá-la, e é esse o problema em questão.

Essa abordagem geralmente funciona bem quando só há uma única implementação possível para cada dependência. Bastando haver duas ou mais implementações possíveis para um mesmo tipo, para que este padrão exija deformações no seu design. O problema fica pior quando você quer duas instâncias da mesma classe, configuradas de forma diferente.

Exemplo:

Contexto

Vou dar o exemplo de um Aluno que estuda. Definimos uma classe Aluno, que possui um método Estudar. Para incrementar o problema, vamos assumir que este método faça uso de um “local de estudo”, modelado como uma propriedade chamada LocalDeEstudo, do tipo ILocalDeEstudo, uma interface implementada por duas classes: Escrivaninha MesaDeJantar.

Imagine que o problema para resolver é representar dois alunos que estudam em locais diferentes, depois solicitar que ambos estudem. Para o exemplo teremos Aluno1, que estuda numa escrivaninha enquanto o segundo, chamado de Aluno2, estuda em uma Mesa de Jantar.

Problema

Como você preencheria cada instância com uma implementação diferente de ILocalDeEstudo?

Algumas soluções possíveis

Se você pensou em um IF no construtor, esqueça! Isso gera um aumento de complexidade equivocado, um erro de estratégia que penaliza o design. Afinal, sobre dependências, você não deveria precisar saber sobre o contexto ao qual é colocado, deveria apenas fazer seu trabalho, desde que tenha sua dependência e necessidades atendidas.

Se você pensou em adicionar um argumento(string, int, enum) ao construtor para que este seja usado na escolha da implementação, esqueça.

Se você pensou na possibilidade da própria classe Aluno solicitar a um Service Locator uma instância que implemente ILocalDeEstudo, então o Service Locator terá um problema a resolver. Saber qual implementação de ILocalDeEstudo você precisa.

Se você pensou que o é responsabilidade de quem criou a instância da classe Aluno, definir explicitamente qual é a dependência necessária pelo aluno, você está no caminho certo, mas o S do SOLID, está pesando contra você.

Então inevitavelmente, usando Service Locators para esse problema fará com que este seja obrigado a compreender o contexto em que está sendo executado, para determinar a implementação, instância ou mesmo versão correta de um objeto para entregá-lo e assim você terá de poluir seu design para conseguir contextualizá-lo.

2) Factories

Factories e suas variações são muito interessantes, mas oferecem os mesmos problemas dos Service Locators.

Então, isso não é IoC

Você pode está pensando em diversas outras soluções para resolver o problema, até usar AOP para contextualizar o Factory ou o Service Locator, mas qualquer abordagem que exija intervenção na  classe que necessita da dependência, está gerando em algum nível conflito com princípios do SOLID. Toda a complexidade deste cenário existe porque não há inversão de controle, mas uma divisão de responsabilidades, entre a classe dependente e o Service Locator ou FactoryPara que haja de fato inversão de controle, é necessário delegar a construção das dependências, completamente. Tentar controlar o fluxo de execução é um erro grave.

Sugestão

Injeção de dependência é uma excelente solução em conjunto com IoC. Embora Factories possam fazer IoC, acredito que a gestão do código dos factories pode ser algo extremamente complexo e extenso. Para resolver essa complexidade, sugiro containers. A proximidade do IoC do DI é exatamente essa, a inversão de controle só é real, quando usando injeção de dependência. Mas cuidado, Injeção de Dependência, sem container, é traumático e algo extremamente e trabalhoso.

E lembre-se, somente o nível mais elevado do sua arquitetura deve solicitar instâncias ao seu container / Factory / Service Locator. No caso do ASP.NET, você pode configurar factories, eles podem usar um container para obter todo o grafo de dependências desde a UI até o nível mais baixo. No ASP.NET MVC também é possível, e é até mais simples, com mais exemplos. Windows Services, Consoles não possuem abstração e não há alternativa**, apenas codificar manualmente, mas lembre-se sempre, somente Entry Points devem solicitar instâncias (acho que essa dica soluciona a questão).

Grande abraço a todos.

* Macoratti é um excelente site, mas para quem está aprendendo. Que fique claro meu orgulho por ter feito parte dos primeiros leitores dos artigos relativos a .Net, isso em ~2003.

** Existem frameworks que ajudam a abstrair Windows Services, como TopShelf.

Leia o artigo do Martin Fowler ( http://martinfowler.com/articles/injection.html )

[update][2017-05-04]

Não se convenceu com exemplos acima? Ok, vou apresentar um novo exemplo:

Mail Services (v1)

Vamos supor que eu tenha uma interface IMailService, como abaixo:

public interface IMailService
{
    void SendMail(MailMessage messageToSend);
}

Premissas:

Agora vou assumir que meu sistema deve utilizar em 90% das ações um SMTP server próprio da empresa enquanto para 10% das necessidades de envio de email, utilize MailChimp ou algum serviço na nuvem parecido. Olhando para a implementação, assumo que tenho 2 implementações possíveis para envio de email: MailChimpMailService e SMTPMailService. Para dar vida ao exemplo, vamos supor que MailChimpMailService envie um HTTP POST para um endereço de api, enquanto SMTPMailService conecta a um servidor SMTP comum. Pronto, já temos o problema evidenciado.

Explicação:

Quem quer que consuma essa interface, não deve se preocupar com a forma como o email é enviado. Seja com o envio para uma API REST, uma conexão SMTP, o insert em diversas tabelas de um banco, ou mesmo o envio para um Message Broker. Quem consome essa interface, deve saber construir um email, para envio. Nada mais. E esse é o ponto chave que faz com que a abordagem escolhida pela Microsoft e hoje em dia por toda a comunidade .NET esteja errada. Falta algo mais.

Mail Services (v2)

Não está convencido, vou piorar. Pense que o MailChimp criou uma nova forma de enviar email, em vez de uma API REST, você simplesmente conectando ao Servidor SMTP deles. Então vc pode jogar a classe MailChimpMailService no lixo (feliz) e passar a usar apenas a classe SMTPMailService. Isso resolveria a questão? Não, pois ainda: meu sistema deve utilizar em 90% das ações um SMTP server próprio da empresa enquanto para 10% das necessidades de envio de email, utilize MailChimp ou algum serviço na nuvem parecido.

Então, a complexidade continua ali, onde não deveria existir. Por precisar usar mais de uma configuração para a resolução de uma dependência.

Até há soluções para isso, mas a forma como a Microsoft escolheu apresentar Injeção de Dependência, sem IoC, chega a ser criminosa. Mas isso é culpa do projeto Castle Windsor, e esse problema começa em 2003!

O Cloud Native .NET é meu principal projeto.

Onde empenho energia para ajudar, acompanhar, direcionar Desenvolvedores, Líderes Técnicos e jovens Arquitetos na jornada Cloud Native.

Conduzo entregando a maior e mais completa stack de tecnologias do mercado.

Ao trabalhar com desenvolvedores experientes, eu consigo usar seu aprendizado com .NET, banco de dados, e arquitetura para encurtar a jornada.

Ao restringir à desenvolvedores .NET eu consigo usar do contexto de tecnologias e problemas do seu dia-a-dia, coisas que você conhece hoje, como WCF, WebForms, IIS e MVC, por exemplo, para mostrar a comparação entre o que você conhece e o que está sendo apresentado.

É assim que construímos fundamentos sólidos, digerindo a complexidade com didática, tornando o complexo, simples.

É assim que conseguimos tornar uma jornada densa, em um pacote de ~4 meses.

Eu não acredito que um desenvolvedor possa entender uma tecnologia sem compreender seus fundamentos. Ele no máximo consegue ser produtivo, mas isso não faz desse desenvolvedor um bom tomador de decisões técnicas.

É preciso entender os fundamentos para conseguir tomar boas decisões.

3 Comentários

  1. André

    Olá Luiz!
    Primeiramente, parabéns pelo post… e foi interessante que um post leva à outro e cheguei até este, legal!
    Mas, senti falta da solução concreta, seja pro exemplo do aluno, seja praquele do email. Conseguiria passar qual seria a solução ideal nesses casos?
    Grande abraço

    Responder
    • Luiz Carlos Faria

      André, reli o texto e a explicação está nele. É abstrato. Mas falar de código sem código é um “”músculo” que precisa ser estimulado, e uma intenção adjacente do texto é essa.
      De qualquer forma eu vou trabalhar em cima desse texto para tentar melhorar a explicação.

      Responder
  2. Rafael

    Estou estudando o assunto e excelente a sua reflexão, todavia não consegui chegar à solução do problema. Seria de grande ajuda se pudesse publicar em forma de código como o amigo já comentou.

    Responder

Enviar um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.

Luiz Carlos Faria

Mensagem do Autor

Espero que goste desse post. Não deixe de comentar e falar o que achou.

Se acha que esse post pode ajudar alguém que você conheça, compartilhe!

 

Lives

Fique de olho nas lives

Fique de olho nas lives no meu canal do Youtube, no Canal .NET e nos Grupos do Facebook e Instagram.

Aceleradores

Existem diversas formas de viabilizar o suporte ao teu projeto. Seja com os treinamentos, consultoria, mentorias em grupo.