Capítulo 18: Injeção de Dependência, Logging Estruturado e Observabilidade
Capítulo 18: Injeção de Dependência, Logging Estruturado e Observabilidade
Especialização em Sistemas com C# • C# 12 & .NET 8 • POO Moderna, LINQ, Concorrência e Arquitetura Corporativa
🗺️ Mapa Conceitual de Escopos de DI e Rastreabilidade Distribuída
flowchart TD
subgraph DI_CONTAINER["Microsoft.Extensions.DependencyInjection"]
Singleton["Singleton (1 Instância para todo o ciclo de vida do Host)"]
Scoped["Scoped (1 Instância por Requisição HTTP / IServiceScope)"]
Transient["Transient (Nova Instância criada a cada injeção)"]
end
subgraph HTTP_REQUEST["Ciclo de Vida da Requisição Web"]
Req["Início do Request HTTP"] --> CriarEscopo["Cria IServiceScope"]
CriarEscopo --> ResolverControllers["Resolve Endpoints & Serviços Scoped"]
ResolverControllers --> TerminarReq["Fim da Resposta HTTP"]
TerminarReq --> DisposeEscopo["Chama Dispose em todos os objetos Scoped do Escopo"]
end
subgraph OBSERVABILIDADE["Pilares de Observabilidade"]
Logs["Logs Estruturados (ILogger + Templates Semânticos)"]
Metrics["Métricas (Meter & Counter para Prometheus)"]
Traces["Distributed Tracing (ActivitySource & W3C TraceContext)"]
end
Scoped -.-> CriarEscopo
HTTP_REQUEST --> OBSERVABILIDADE
style DI_CONTAINER fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style HTTP_REQUEST fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style OBSERVABILIDADE fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
🏛️ 1. O Contêiner de Injeção de Dependência do .NET
O .NET possui um container de Injeção de Dependência (DI) nativo de alta performance no pacote Microsoft.Extensions.DependencyInjection. Ele elimina a necessidade de inicializar classes concretas manualmente com new, desacoplando componentes através de interfaces.
Matriz Comparativa de Tempos de Vida (Service Lifetimes):
| Ciclo de Vida | Método de Registro | Instanciação | Caso de Uso Recomendado |
|---|---|---|---|
| Transient | services.AddTransient<T, Impl>() |
Criado toda vez que é injetado | Serviços leves sem estado (stateless algorithms, formatadores). |
| Scoped | services.AddScoped<T, Impl>() |
Uma instância por conexão/requisição HTTP | DbContext do Entity Framework, Repositórios, Sessão de Usuário. |
| Singleton | services.AddSingleton<T, Impl>() |
Criado apenas uma vez e compartilhado | Clientes HTTP (HttpClientFactory), Caches em memória, Módulos de Métricas. |
⚠️ 2. O Antipadrão Crítico: Dependência Cativa (Captive Dependency)
Uma Dependência Cativa ocorre quando um serviço de ciclo de vida mais longo injeta um serviço de ciclo de vida mais curto.
Exemplo Perigoso:
Injetar um repositório Scoped (DbContext) dentro de um serviço Singleton.
- O que acontece: O repositório Scoped ficará aprisionado no Singleton e nunca será liberado pelo Garbage Collector.
- Consequências: Vazamento catastrófico de conexões SQL, deadlocks de transações concorrentes e dados corrompidos entre usuários distintos.
[!CAUTION] Validação de Escopo em Desenvolvimento O ASP.NET Core habilita
ValidateScopes = truepor padrão em modoDevelopment. Ao rodar localmente, o container lançará umaInvalidOperationExceptionna inicialização se detectar uma dependência cativa. Nunca desative essa trava em produção sem justificativa de arquitetura.
📊 3. Logging Estruturado com ILogger<T> e Source Generators
A interpolação de strings em logs (logger.LogInformation($"Cliente {id} efetuou compra")) é uma má prática corporativa:
- Aloca strings intermediárias desnecessárias na Heap a cada chamada.
- Destrói os metadados pesquisáveis em agregadores de telemetria como Elasticsearch, Datadog ou Grafana Loki.
O padrão correto é o uso de Templates Semânticos de Mensagem ou Source Generators ([LoggerMessage]):
// 1. Template Semântico (Propriedades preservadas no JSON do Log):
_logger.LogInformation("Ordem {PedidoId} criada com sucesso para o cliente {ClienteId}. Valor: {Valor:C}",
pedido.Id, pedido.ClienteId, pedido.ValorTotal);
// 2. High-Performance Source Generator (C# 10+ / .NET 8 - Zero Alocação):
public static partial class LogExtensoes
{
[LoggerMessage(EventId = 101, Level = LogLevel.Information, Message = "Processando pagamento da transação {TransacaoId} no gateway {Gateway}")]
public static partial void LogProcessandoPagamento(this ILogger logger, Guid transacaoId, string gateway);
}
🔭 4. Observabilidade Moderna: OpenTelemetry no .NET 8
No .NET 8, a telemetria não é uma biblioteca externa adicionada como gambiarra; ela é integrada ao runtime da BCL através de:
System.Diagnostics.ActivitySource: Gera Spans de Distributed Tracing compatíveis com W3C TraceContext e Jaeger/Zipkin.System.Diagnostics.Metrics.Meter: Exporta contadores e histogramas diretamente para Prometheus.
💻 5. Laboratório Prático de Arquitetura: DI Nativo, Escopos e Logging
O exemplo abaixo demonstra a montagem de um contêiner completo sem ASP.NET, demonstrando o ciclo de vida Scoped com descarte seguro (IDisposable), injeção por construtor e logging estruturado.
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace TreinamentoDotNet.InjecaoDependencia;
// 1. Contratos e Serviços de Negócio
public interface IRelatorioService
{
void GerarRelatorio(string titulo);
}
public interface ITransacaoContexto : IDisposable
{
Guid TransacaoId { get; }
}
public class TransacaoContexto : ITransacaoContexto
{
public Guid TransacaoId { get; } = Guid.NewGuid();
public TransacaoContexto()
{
Console.WriteLine($" [TransacaoContexto] Instanciado: {TransacaoId}");
}
public void Dispose()
{
Console.WriteLine($" [TransacaoContexto] Dispose executado com sucesso: {TransacaoId}");
}
}
public class RelatorioService : IRelatorioService
{
private readonly ITransacaoContexto _contexto;
private readonly ILogger<RelatorioService> _logger;
public RelatorioService(ITransacaoContexto contexto, ILogger<RelatorioService> logger)
{
_contexto = contexto;
_logger = logger;
}
public void GerarRelatorio(string titulo)
{
_logger.LogInformation("Gerando relatório '{Titulo}' associado à transação {TransacaoId}",
titulo, _contexto.TransacaoId);
}
}
// 2. Execução e Validação de Escopos
public class Program
{
public static void Main()
{
Console.WriteLine("=== Laboratório: Container de Injeção de Dependência e Logging ===");
// Configuração do ServiceCollection
var servicos = new ServiceCollection();
// Configuração de Logging no Console
servicos.AddLogging(builder =>
{
builder.AddSimpleConsole(options =>
{
options.IncludeScopes = true;
options.SingleLine = true;
options.TimestampFormat = "[HH:mm:ss] ";
});
builder.SetMinimumLevel(LogLevel.Information);
});
// Registro de Serviços por Ciclo de Vida
servicos.AddScoped<ITransacaoContexto, TransacaoContexto>();
servicos.AddTransient<IRelatorioService, RelatorioService>();
// Build do ServiceProvider com validação rigorosa de escopos
using ServiceProvider container = servicos.BuildServiceProvider(validateScopes: true);
Console.WriteLine("\n[1] Criando Escopo HTTP 1:");
using (IServiceScope escopo1 = container.CreateScope())
{
var relatorio1 = escopo1.ServiceProvider.GetRequiredService<IRelatorioService>();
relatorio1.GerarRelatorio("Balanço Trimestral Q1");
var relatorio2 = escopo1.ServiceProvider.GetRequiredService<IRelatorioService>();
relatorio2.GerarRelatorio("DRE Consolidada");
// Note que ambas as chamadas no escopo 1 compartilham o MESMO TransacaoId!
} // Ao sair do bloco using, Dispose() de TransacaoContexto é chamado automaticamente!
Console.WriteLine("\n[2] Criando Escopo HTTP 2 (Nova Requisição Isolada):");
using (IServiceScope escopo2 = container.CreateScope())
{
var relatorio3 = escopo2.ServiceProvider.GetRequiredService<IRelatorioService>();
relatorio3.GerarRelatorio("Auditoria Fiscal Externa");
// Um NOVO TransacaoId foi gerado para este escopo.
}
Console.WriteLine("\nExecução de escopos finalizada com integridade total.");
}
}