.NET Core - Injeção de dependência (DI)


Neste artigo vamos recordar os conceitos da injeção de dependência no ambiente .NET Core.

O Net Core suporta o Padrão de injeção de dependência (DI), que é uma técnica para obter a inversão de controle (IoC) entre classes e suas dependências.

E você não precisa de nenhuma biblioteca externa de injeção de dependência para usar este recurso no .NET Core porque ele esta integrado ao .NET Core.

Mas o que é  dependência ?

Uma dependência é qualquer outro objeto que é requerido por um objeto.

E o que é Dependency Injection ou injeção de dependência ?

Basicamente, temos dois princípios importantes para definir a injeção de dependência:

1- “Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações. “

2- “As abstrações não devem depender de detalhes. Os detalhes devem depender de abstrações. “


Esse é o mantra recitado pela comunidade e encontrado no livro "Princípios, padrões e práticas ágeis em C#" de  Robert C. Martin e Micah Martin.

Os módulos de alto nível são módulos que contém as regras de negócio e os módulos de baixo nível são módulos que contém implementação de infraestrutura.

Aqui, entendemos  'abstração'  como interfaces e classes abstratas e por 'detalhes' entendemos como classes concretas.

Vamos ilustrar com um exemplo.

Vamos supor que temos duas classes :

1- A classe SaudacaoService  e a classe Program

   

Não existe dependência nenhuma entre essas duas classes.

Mas a situação vai mudar de figura se eu precisar saudar alguém e ajustar o código conforme abaixo:

Agora, a classe Program depende de SaudacaoService ou em outras palavras, SaudacaoService é uma dependência da classe Program.

No código estamos instanciando diretamente a classe SaudacaoService, e, esta abordagem não é uma boa estratégia se pensarmos em termos de escalabilidade.

Ao fazermos isso, nosso código se torna DEPENDENTE de outros componentes ou, em outras palavras, houve um AUMENTO DE ACOPLAMENTO em nosso código.

Devemos evitar essa situação pelos seguintes motivos:

- Para substituir SaudacaoService por uma implementação diferente, a classe Program tem que ser modificada;
- Se SaudacaoService tiver muitas dependências, elas devem ser configuradas pela classe. Além disso, a classe SaudacaoService é responsável pelo tempo de vida de suas dependências;

Desta forma usando essa implementação fica difícil fazer testes de unidade porque nosso código usa classes concretas.

Aqui podemos usar a injeção de dependência para nos ajudar e para ilustrar como este recurso é importante para desacoplar código.

Para poder usar a injeção de dependência temos as seguintes diretrizes:

Então com base nessas premissas vamos ajustar a nossa aplicação para poder usar o recurso da injeção da dependência.

Usando a injeção de dependência(DI)

Nosso primeiro exemplo usando a DI será um aplicativo Net Core Console porque muitas vezes as pessoas pensam que o recurso interno de injeção de dependência pode ser usado apenas em um contexto da WEB (ASP.NET CORE), mas isso não é correto.

Veja a seguir o código modificado :

Neste código criei uma nova interface :  ISaudacaoService com uma assinatura de método DizOla() que retorna uma string.

A classe SaudacaoService agora implementa a interface, e, no construtor da classe estamos gerando um Id exclusivo que é atribuído à propriedade Id do tipo Guid.

Instalando o contâiner DI e registrando serviços

Vamos então fazer a injeção da dependência e para isso precisamos instalar o pacote Microsoft.Extensions.DependencyInjection no projeto Console. (Nas aplicações ASP .NET Core isso não é preciso)

Com o contêiner DI instalado  precisamos criar o contêiner e registrar todos os nossos serviços.

Podemos registrar diferentes tipos de serviços, dependendo de seu TEMPO DE VIDA:

   

  1. SINGLETON: O contêiner sempre retorna a mesma instância. Deve ser thread-safe;
  2. TRANSIENT: O contêiner sempre retorna uma instância diferente. Os serviços de vida útil transitória são criados cada vez que são solicitados do contêiner de serviço;
  3. SCOPE: O contêiner retorna uma nova instância por Request;

Exemplo de criação de contêiner e registro de serviço:


Aqui criamos o contêiner usando a classe ServiceCollection() e a seguir registramos o serviço ISaudacaoService com o tempo de vida Singleton;

Quando registramos nossos serviços, o próximo passo é solicitar os serviços ao container, mas não podemos fazer isso diretamente, precisamos solicitar o container através do Provedor. Veja um exemplo de código que faz isso:

Neste código solicitamos ao provedor duas instâncias de ISaudacaoService, mas este serviço está registrado como Singleton no Container e por isso o container deve retornar a mesma instância, ou seja: saudadorA e saudadorB devem ser a mesma instância/objeto.

Além disso, ambas as classes devem ter O MESMO CÓDIGO DE HASH e o construtor deve executar APENAS UMA VEZ.

Resultado obtido :

Percebeu a 'mágica' , o construtor foi chamado apenas uma vez e ambas as classes tem o mesmo código hash porque são a mesma classe.

Vamos agora registrar o serviço usando o tempo de vida Transient e ver o resultado:

 services.AddTransient<ISaudacaoService, SaudacaoService>();

Nesse caso, o contêiner deve retornar diferentes instâncias de SaudacaoService porque o tempo de vida Transient gera uma nova instância a cada vez que é solicitado.

Resultado:



Agora o contêiner criou duas instâncias de Saudador e percebemos que o construtor foi chamado duas vezes e cada instância tem um Id e um código Hash diferentes.

Para concluir vamos registrar o serviço usando o tempo de vida Scoped :

 services.AddScoped<ISaudacaoService, SaudacaoService>();

Resultado:

Podemos ver como o container retorna o mesmo objeto, mas lembre-se de que isso se aplica apenas a projetos de aplicativos de console.

Em um ambiente da Web, o tempo de vida SCOPED criará instâncias POR REQUEST.

"Os que confiam no SENHOR serão como o monte de Sião, que não se abala, mas permanece para sempre."
Salmos 125:1

Referências:


José Carlos Macoratti