Olá, tudo bem? Que copa né?! Sermos batidos por 7×1, em casa, não é nada legal, mas poderia ser pior, em vez da Alemanha, a Argentina! Bom, independente do resultado da copa, esse assunto aqui pode realmente tirar seu sono. É comum entrar em discussões eternas sobre o erro de usar IoC e DI, e quero usar esse post para tonar isso mais óbvio.

IoC e DI

Vou usar os termos IoC e DI em todo o post, então vamos detalhar esses 2 acrônimos:

IoC: Inversion Of Control – Inversão de Controle. (Cuidado com a versão em PT-BR da Wikipedia, ela está errada!)

DI: Dependency Injection – Injeção de Dependência.

Um dos melhores posts para explicar IoC e DI está a aqui.

Problemas Comuns

Ao analisarmos projetos e diversos posts em diversos blogs, encontramos erros e mais erros na compreensão dos conceitos de IoC e DI. Embora sejam por uma questão de design inseparáveis, é possível ver IoC sem DI e DI sem IoC, e é aí que mora o problema.

Utilização de container IoC, sem fazer IoC e sem fazer DI

Abaixo mostro um exemplo:

Erros do exemplo:

1) Se uma classe requisita uma ou mais dependências ao container então não há IoC, muito menos DI. Neste caso o container IoC tem papel de Factory ou Service Locator, como preferir.

2) Resolver dependências baseadas exclusivamente em tipos gera erros de design. Ao escolher essa abordagem, está abdicando de:

  • Ter em seu container, registros/configurações diferentes para uma mesma classe
  • Ter em seu container, registros/configurações de uma segunda classe, que implemente o mesmo tipo.

Esses erros fazem com que você desenhe classes com maior acoplamento e acumulo responsabilidades. Você troca a possibilidade de ter uma implementação configurada diversas vezes, de forma diferente, para dar para a implementação a responsabilidade de compreender em qual contexto está inserido, e com isso, a demanda de tomar a decisão do que fazer.

Forma correta:

As diferenças:

  • Não é responsabilidade do HomeController saber que existe um container, sequer a dependência do container é necessária para esta classe.
  • Embora o primeiro exemplo não explore a passagem de informações para a construção do objeto, esse seria um problema. Já no exemplo acima, não há essa relação entre HomeController e o Container.

Um ponto, que não abordei no exemplo mas vou abordar a seguir é a necessidade de mais de uma instância/implementação para um mesmo tipo(interface).

O problema do Register Resolve Release patterndependency-injection

O problema não é o padrão em si, mas os exemplos proliferados na grande internet. Os exemplos conduzem a cometer o mesmo erro recorrente: Modelar errado!

Esse é o ponto que as pessoas geralmente não se atentam e quando explico: não entendem.

Imagine que você começou a criar um sistema e/ou serviço hoje em D0. Ao desenhar a infraestrutura de Logs, você desenhou uma interface ILogger semelhante à interface do exemplo 3:

A primeira implementação de ILogger que lhe vem à cabeça, dado seus requisitos, é o FileLogger. Passaram 2 dias, e no terceiro dia, D2, você criou 2 serviços, respectivamente Service1 e Service2, que dependem de uma implementação da interface ILogger para que possam gravar seus logs de operação. O exemplo 4, ajuda a elucidar.

Agora, em D89, com 90 dias de desenvolvimento, seu sistema/serviço entrou em produção, e com mais 10 dias, houve a necessidade de segmentar os logs de Service1 e Service2. Independente da origem da necessidade, vou supor que essa exista. Agora você precisa que Service1 gere Logs no arquivo Service1.Log enquanto Service2 gere logs em Service2.log.

Vou dar alguns exemplos de solução:

  • Mudar a interface ILogger para que o método Log receba também o parâmetro FileName, obviamente suas abstrações também receberiam.
  • Mudar a interface ILogger para que o método Log receba também algum parâmetro de marcação que ajude as implementações de ILogger a descobrirem onde devem gravar o log (Enums, parâmetros de controle, etc)
  • Implementar um ILogger diferente, que gerencie onde gravar os Logs e diga para o ILogger, mantendo a compatibilidade com os consumidores e executando os 2 passos descritos nos tópicos anteriores.

Se você considerou uma dessas 3 opções a mais adequada para o cenário, tenho uma boa notícia para você: Este post é totalmente voltado para os erros que você comete no dia-a-dia. E sim, as 3 opções que dei estão completamente equivocadas!!! A má notícia, é que todas, ou pelo menos na maioria das vezes que optou por alguma dessas abordagens, você cometeu um sacrilégio.

Entenda:

Seu container provavelmente suporta a definição/registro de mais de uma implementações para um mesmo tipo (ILogger), podendo nomeá-las e configurá-las de forma independente. Provavelmente você não usa essa feature, pois 95% dos exemplos baseados em Register Resolve Release pattern, consistem em mostrar exatamente a abordagem simples, quando só há, somente, uma única implementação possível por tipo registrado.

Olhe esse exemplo de post do blog “Developer Tools Blogs” da própria Microsoft: 

CodeLens for Git improvements in Visual Studio 2013 Ultimate Update 3 RC 

Nesse caso, estamos falando do Roslyn, as implicações para este cenário é de que só pode existir no container uma única implementação de ISyntaxFactsService, bem como ISemanticFactsServices, e essa restrição limita suas possibilidades de modelagem.

Outro exemplo, está nas páginas do SimpleInjector:

https://simpleinjector.codeplex.com/

https://simpleinjector.codeplex.com/wikipage?title=Using%20the%20Simple%20Injector&referringTitle=Home

Interferência no Design

Se você considerou uma ou mais opções acima, como solução para o problema, você cometeu um erro de design. Me desculpe, não é algo subjetivo, ou com mérito de um “depende!”. O problema acontece, porque você não tomou uma decisão correta. Para este problema, sabemos que existe um erro comum, e diversas soluções corretas:

Aqui apresento uma das diversas soluções corretas, e a que considero a mais simples, que consiste em adicionar uma propriedade (estado) à classe FileLogger, como mostra o exemplo 5:

No container, ILogger seria registrado diversas vezes, tantas quantas seu FileLogger, precisar ser configurado diferente, como mostro no exemplo 6.

Onde está o erro?

As abordagens citadas como erradas, tendem a aumentar o acoplamento entre o consumidor e seu provedor.

Se optar pela adição do parâmetro FileName: A iteração passa a ser mais complexa, pois o consumidor precisa conhecer informações específicas da implementação do provedor, ignorando a abstração, e limitando-a.

Se optar pela utilização de parâmetros de marcação, a implementação do FileLogger teria sua complexidade aumentada, pois haveria necessidade de algum tipo de DE-PARA, ou em hard code ou usando configurações, discovery, etc.

As duas opções aumentam o acoplamento, e quanto maior o acoplamento, pior fica o design, menor é a testabilidade, maior é a complexidade.

Só para mostrar o tamanho do problema, imagine a necessidade de evolução que removesse o FileLogger, substituindo-o por um QueueLogger. Essa mudança não deveria afetar os consumidores de ILogger, mas dadas as características de complexidade adicionadas à interface, FileName deixaria de fazer sentido. Se FileName for uma propriedade, exclusiva da classe FileLogger, ótimo, nada precisa ser feito, mas se for um parâmetro, refatorar os consumidor é necessário.

Ainda há a terceira solução, onde induzia a criação de um gerenciador para que este, pudesse tomar a decisão, com base em alguma informação de contexto. Bom, nesse caso, uma classe a mais, não faz o menor sentido, dado os diversos padrões que podem ser aplicados para a solução.

Conclusões

Manter sua cabeça aberta, garantindo que a utilização ou não de containers não influencie seu design, é um bom ponto de partida. Sempre que você optar por configurar mais e programar menos, terá ganhos significativos. Não polua seu modelo com decisões que podem ser abstraídas por configurações, não deixe de criar várias implementações para uma determinada interface ou tipo base, se essa for sua real necessidade. Contornar esses problemas por causa do Container ou do exemplo padrão encontrado em um site, é um puta erro.

Grande abraço, volto em breve. Aliás, enquanto isso, há algumas leituras relevantes sobre esse assunto no tópico a seguir! Não perca!

Leituras recomendadas

Compose object graphs with confidence | http://blog.ploeh.dk/2011/03/04/Composeobjectgraphswithconfidence/

How not to do dependency injection – the static or singleton container | http://www.devtrends.co.uk/blog/how-not-to-do-dependency-injection-the-static-or-singleton-container

How not to do dependency injection – using xml over fluent configuration  | http://www.devtrends.co.uk/blog/how-not-to-do-dependency-injection-using-xml-over-fluent-configuration

Constructor over-injection anti-pattern | http://jeffreypalermo.com/blog/constructor-over-injection-anti-pattern/

Comente, compartilhe, curta!

Logo abaixo desse texto você encontra os Posts Relacionados, e botões de compartilhamento, em seguida a sessão de comentários!

Gostou? Então aproveite para curtir, compartilhar e enviar comentários, dúvidas ou sugestões.

Conheça o Grupo Arquitetura de Softwate | .NET: Facebook e Telegram
Luiz Carlos Faria: Site, Youtube, Facebook, Twitter, Telegram, Linkedin e Email