Injeção de Dependência (DI) em .NET Core: O Que É e Por Que Usar
Se você está desenvolvendo aplicações com .NET Core ou ASP.NET Core, certamente já se deparou com o termo Injeção de Dependência (DI). Este não é apenas um modismo de programação, mas um padrão de design fundamental que torna o código mais limpo, testável e fácil de manter.
O ASP.NET Core adota a Injeção de Dependência como um pilar central de sua arquitetura. Entender o que é e como usá-la corretamente é essencial para escrever código de qualidade.
Neste guia extenso e detalhado, vamos desvendar o conceito de DI, explorar seus benefícios e mostrar como o framework a gerencia para você.
O Que é Injeção de Dependência (DI)?
Para entender a Injeção de Dependência, precisamos primeiro entender o que é uma Dependência.
Imagine uma classe A
(por exemplo, um ServicoDePedidos
) que precisa usar uma funcionalidade de uma classe B
(por exemplo, um RepositorioDeDados
) para salvar informações no banco de dados. Dizemos que a classe A
depende da classe B
.
O Problema da Dependência Rígida (Tightly Coupled)
Sem DI, você faria algo assim:
public class ServicoDePedidos
{
// A dependência é criada DENTRO da classe.
private readonly RepositorioDeDados _repositorio = new RepositorioDeDados();
public void CriarPedido()
{
// ... usa _repositorio para salvar dados
}
}
Neste exemplo, a classe ServicoDePedidos
está rigidamente acoplada (ou tightly coupled) ao RepositorioDeDados
.
Impossibilidade de Teste: Se você quiser testar
ServicoDePedidos
, você também estará testando oRepositorioDeDados
(e o banco de dados real), o que torna o teste lento e complexo.Dificuldade de Troca: Se você precisar mudar o banco de dados (de SQL Server para NoSQL, por exemplo), você terá que alterar o código dentro da classe
ServicoDePedidos
.
A Solução da Inversão de Controle (IoC)
A Injeção de Dependência é a principal forma de implementar o princípio da Inversão de Controle (IoC).
Em vez de a classe ServicoDePedidos
criar sua própria dependência (new RepositorioDeDados()
), alguém de fora (o container de DI) fornece a dependência a ela. A responsabilidade de criar a dependência é invertida.
A DI, então, é o processo de fornecer as dependências de um objeto por meio de seu construtor, propriedades ou chamadas de método, em vez de permitir que o objeto as crie internamente.
Como a DI Resolve o Acoplamento
A chave para o desacoplamento está no uso de Interfaces.
Defina uma interface para a funcionalidade:
IRepositorioDeDados
.Faça com que a classe concreta a implemente:
RepositorioDeDados : IRepositorioDeDados
.Receba a dependência no construtor da classe, usando a interface:
public class ServicoDePedidos
{
private readonly IRepositorioDeDados _repositorio;
// A dependência é "injetada" (passada) no construtor.
public ServicoDePedidos(IRepositorioDeDados repositorio)
{
_repositorio = repositorio;
}
public void CriarPedido()
{
// ... usa _repositorio, sem saber QUEM o implementa!
}
}
Agora, a classe ServicoDePedidos
só conhece o contrato (IRepositorioDeDados
), mas não a implementação específica.
Por Que Usar a Injeção de Dependência? (Os Benefícios)
O DI é mais do que uma boa prática, ele oferece benefícios tangíveis ao longo de todo o ciclo de vida do software.
1. Testabilidade (O Benefício Principal)
Com a DI, você pode injetar mocks (objetos falsos) ou stubs durante os testes unitários.
Quando você testa
ServicoDePedidos
, você injeta umRepositorioDeDadosFake
que não toca no banco de dados, mas apenas simula o comportamento de salvar dados.Isso torna os testes rápidos, isolados e confiáveis.
2. Manutenibilidade e Flexibilidade
Se você precisar trocar a implementação de um serviço (por exemplo, mudar o provedor de e-mail), você só precisa criar a nova classe que implementa a interface antiga e registrar a nova implementação no container de DI.
O código existente (que depende apenas da interface) continua funcionando sem nenhuma alteração.
3. Código Mais Limpo e Focado (SRP)
O DI ajuda a seguir o Princípio da Responsabilidade Única (SRP). Uma classe deve ser responsável por sua lógica de negócios, e não pela criação das suas dependências. Ao receber as dependências, a classe se torna mais coesa e foca apenas em sua responsabilidade principal.
Injeção de Dependência no ASP.NET Core
O framework .NET Core vem com um container de DI nativo e leve. Você não precisa instalar bibliotecas externas (embora possa, se quiser recursos avançados como o Autofac ou o Ninject).
O registro de todas as suas interfaces e classes é feito no método ConfigureServices
(em projetos mais antigos) ou diretamente no Program.cs
(em projetos .NET 6+).
O Program.cs
e o Container de Serviços
Toda a configuração de DI é feita através da propriedade builder.Services
, que representa a Coleção de Serviços da aplicação.
var builder = WebApplication.CreateBuilder(args);
// Configuração dos serviços do aplicativo (DI)
builder.Services.AddScoped<IRepositorioDeDados, RepositorioDeDados>();
builder.Services.AddTransient<IServicoDeEmail, ServicoDeEmail>();
builder.Services.AddSingleton<ICacheDeDados, CacheDeDados>();
// ... restante da configuração
O Conceito Crucial: Tempo de Vida (Lifetime) dos Serviços
Ao registrar um serviço, você deve escolher um dos três principais tempos de vida. Isso define quando o container de DI cria uma nova instância do objeto e quando essa instância é descartada.
Tempo de Vida | Descrição | Quando é Criado |
Singleton | Uma única instância é criada e compartilhada por toda a aplicação, durante todo o tempo de vida do aplicativo. | Na primeira vez que é solicitado (ou na inicialização, se configurado). |
Scoped | Uma nova instância é criada uma vez por solicitação HTTP (conexão). É o mais comum em aplicações Web. | Na primeira vez que é solicitado dentro da mesma solicitação HTTP. |
Transient | Uma nova instância é criada toda vez que o serviço é solicitado, mesmo dentro da mesma solicitação. | Toda vez que o serviço é solicitado. |
Quando Usar Cada Um?
Cenário | Tempo de Vida Recomendado |
Acesso a Banco de Dados (EF Core) | Scoped - Você quer que o mesmo DbContext seja usado em toda a solicitação para garantir que todas as operações façam parte de uma única transação lógica. |
Cache de Configurações ou Dados | Singleton - O estado é o mesmo para todos e deve ser compartilhado por todos os usuários. |
Serviços Leves e Sem Estado | Transient - Ideal para serviços que apenas executam uma operação rápida e não armazenam nenhuma informação entre chamadas, como um ConversorDeMoeda . |
A Injeção na Prática (No Construtor)
Depois de registrar os serviços no Program.cs
, o framework ASP.NET Core se encarrega de injetá-los automaticamente sempre que necessário.
O método mais comum e recomendado é a Injeção via Construtor:
Exemplo em um Controller ASP.NET Core
O container de DI do .NET Core lê o construtor do Controller
, identifica o tipo de serviço solicitado (IRepositorioDeDados
) e o fornece (injeta) com base no tempo de vida definido.
// 1. O container de DI sabe que deve injetar uma instância de IRepositorioDeDados.
public class MeuController : ControllerBase
{
private readonly IRepositorioDeDados _repositorio;
// 2. O .NET Core injeta a dependência IRepositorioDeDados aqui.
public MeuController(IRepositorioDeDados repositorio)
{
_repositorio = repositorio;
}
[HttpGet]
public IActionResult GetProdutos()
{
// 3. O controller usa o serviço injetado.
var produtos = _repositorio.BuscarTodos();
return Ok(produtos);
}
}
Conclusão: DI é Qualidade de Código
A Injeção de Dependência é a espinha dorsal de qualquer aplicação .NET Core moderna, promovendo um código mais:
Desacoplado: Classes não sabem como suas dependências são criadas.
Testável: Facilidade em injetar mocks para testes unitários.
Flexível: Simplicidade na troca de implementações de serviço.
Dominar os tempos de vida (Singleton, Scoped, Transient) e a injeção via construtor são os primeiros passos para desenvolver sistemas robustos e de alto desempenho na plataforma .NET. Ao adotar esse padrão, você não está apenas seguindo uma moda, mas elevando a qualidade e a longevidade de todo o seu projeto.
Comentários
Postar um comentário