Quando se estuda backend, um dos primeiros conceitos importantes que aparece é a Injeção de Dependência (DI - Dependency Injection). Atualmente, o ciclo de desenvolvimento é rápido e exige atualizações constantes. Acontece que ao realizar alterações no seu código, você não deseja ter efeitos colaterais e erros inesperados que afetam outras partes do seu código. Para minimizar o máximo esse problema a solução é criar aplicações modulares onde as dependências entre esses módulos sejam reduzidas e você tenha um baixo acoplamento e uma alta coesão.
Toda classe do seu sistema precisa de “coisas” para funcionar, essas “coisas ” são chamadas de dependência.
Exemplo real:
public class PedidoService
{
public void FinalizarPedido()
{
var emailService = new EmailService();
emailService.Enviar("Pedido finalizado!");
}
}
Nesse exemplo, o PedidoService
depende de EmailService
para enviar e-mails. Até o momento parece ok, mas existe um grande problema, o código está acoplado.
PedidoService
conhece demais sobre o funcionamento do EmailService
, ele mesmo cria new
e usa. Ou seja, está preso, travado naquela implementação, que resulta em um código difícil de manter, testar e evoluir.
Situação | Problema gerado |
---|---|
Quero trocar o envio de e-mail por SMS | Tem que abrir PedidoService e trocar tudo |
Quero testar PedidoService sem enviar e-mail de verdade |
Não dá, porque ele sempre cria EmailService |
Quero usar um serviço de e-mail diferente | Tem que mexer dentro de PedidoService |
Tenho vários serviços usando EmailService |
Cada um cria um new EmailService() → duplicação de código, desperdício de memória |
É nesse ponto que nasce um conceito importante: “Quem deve controlar qual serviço eu uso não é minhja classe. É alguém externo”, esse “alguém” é o próprio sistema através de um container de injeção. Desse modo, nosso PedidoService
só vai receber pronto o que ele precisa.
public interface INotificacaoService
{
void Enviar(string mensagem);
}
Aqui estamos dizendo: "Eu não me importo como a mensagem será enviada. Só quero que exista alguém capaz de executar o método Enviar()
." Isso é lindo. Porque o PedidoService não quer saber de e-mail, SMS, WhatsApp, sinal de fumaça... ele só quer enviar.
Enviar por E-mail:
public class EmailService : INotificacaoService
{
public void Enviar(string mensagem)
{
Console.WriteLine("Enviando Email: " + mensagem);
}
}
Agora ele só recebe a dependência via construtor:
public class PedidoService
{
private readonly INotificacaoService _notificacaoService;
// INJEÇÃO DE DEPENDÊNCIA VIA CONSTRUTOR
public PedidoService(INotificacaoService notificacaoService)
{
_notificacaoService = notificacaoService;
}
public void FinalizarPedido()
{
_notificacaoService.Enviar("Pedido finalizado!");
}
}
Porque o construtor é o momento onde o .NET vai "enfiar" dentro da sua classe a dependência correta. É tipo assim:
Esse é o papel do construtor na DI.
O motor por trás disso tudo chama-se: IoC Container (Inversion of Control Container).
É um recurso do .NET que guarda a informação:
Na class Program.cs
builder.Services.AddScoped<INotificacaoService, EmailService>();
builder.Services.AddScoped<PedidoService>();
Pedido | Entrega |
---|---|
Alguém pedir INotificacaoService | Entrega EmailService |
Alguém pedir PedidoService | Entrega PedidoService (e injeta EmailService dentro dele) |
Até agora entendemos quem cria os objetos (IoC Container), como ele sabe o que criar (lendo o construtor) e onde ensinamos essas regras (Program.cs).
Agora a questão é: depois que o IoC cria o objeto… ele fica vivo por quanto tempo? É nesse contexto que nasce os escopos de serviços. Escopo de serviço é nada mais do que: O tempo de vida do objeto dentro da aplicação. Existem 3 tipos principais no .NET
Cria o serviço uma vez só e nunca mais cria outro.
Podemos utilizar o escopo singleton
quando o objeto:
builder.Services.AddSingleton<IMeuServico, MeuServico>();
Serviço de configuração:
public class AppSettingsService
{
public string ApiKey { get; set; } = "123456";
}
Registro:
builder.Services.AddSingleton<AppSettingsService>();
Resultado:
Criar um por requisição HTTP
Um serviço de carrinho de comrpas:
public class CarrinhoService
{
public List<string> Produtos = new();
}
Registro:
builder.Services.AddScoped<CarrinhoService>();
Resultado:
Cria sempre que pedir, não importa quem, cria um novo objeto.
Até na mesma requisição, cada injeção cria uma nova instância
Quando o objeto:
Serviço de validação
public class ValidadorCpfService
{
public bool Validar(string cpf) => cpf.Length == 11;
}
Registro:
builder.Services.AddTransient<ValidadorCpfService>();
| --- | --- | --- | --- | --- |
[ApiController]
[Route("api/[controller]")]
public class PedidoController : ControllerBase
{
private readonly PedidoService _pedidoService;
public PedidoController(PedidoService pedidoService)
{
_pedidoService = pedidoService;
}
}
→ Aí o .NET pensa assim:
"Beleza, preciso montar um PedidoController."
Tudo automático.