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 = true por padrão em modo Development. Ao rodar localmente, o container lançará uma InvalidOperationException na 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:

  1. Aloca strings intermediárias desnecessárias na Heap a cada chamada.
  2. 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.");
    }
}