Executando verificação de segurança...
1

Pare de usar Switch Statements: Keyed Services no .NET - uma abordagem prática

Você já ouviu falar de Keyed Services? Sabe quando usá-los? Já precisou de múltiplas implementações da mesma interface e acabou escrevendo um bloco gigante de switch ou if-else para decidir qual usar?

Neste artigo, vou demonstrar usando um projeto POC no GitHub com todos os detalhes de implementação.

O que são Keyed Services?

Antes do .NET 8, registrar múltiplas implementações da mesma interface era possível, mas resolver uma implementação específica não era algo simples.

Os desenvolvedores normalmente injetavam IEnumerable e selecionavam manualmente as implementações usando condições ou metadados. Com Keyed Services, um recurso nativo de injeção de dependência introduzido no .NET 8, você pode registrar múltiplas classes para a mesma interface usando uma chave.

Por que isso é Importante?

Existem cenários onde podemos reduzir o número de instruções if-else e alcançar uma solução mais limpa usando essas chaves.

Arquiteturas orientadas a eventos se beneficiam particularmente dessa abordagem em termos de manutenibilidade.

Quando Você Deve Usar Keyed Services?

Cenários aplicáveis:

  • Múltiplas implementações de interface
  • Dados em tempo de execução determinando a implementação correta
  • Evitar lógica de switch/if-else
  • Soluções extensíveis

Casos de uso comuns:

  • Event handlers
  • Canais de notificação (Email, SMS)

Como Funciona?

Fluxo

Evento de Entrada
      │
      ▼
EventHandlerFactory
      │
      ▼
Resolução via DI com Chave
      │
      ▼
EventHandler Específico

Interface EventHandlerFactory

internal interface IEventHandlerFactory
{
    Task HandleEvent(string eventName, object eventData);
}

Interface IEventHandler

internal interface IEventHandler
{
    Task HandleAsync(object eventData, CancellationToken cancellationToken);
}

Exemplo de Handler

Três event handlers são demonstrados: PurchaseOrderEventHandler, InvoiceEventHandler, PaymentEventHandler.

internal class PaymentEventHandler : IEventHandler
{
    public Task HandleAsync(object eventData, CancellationToken cancellationToken)
    {
        // Implemente a lógica para tratar eventos de pagamento aqui
        return Task.CompletedTask;
    }
}

Implementação Inicial do EventHandlerFactory

internal class EventHandlerFactory(IServiceProvider serviceProvider) : IEventHandlerFactory
{
    public const string PurchaseOrder = "PurchaseOrder";
    public const string PaymentProcessed = "PaymentProcessed";
    public const string InvoiceProcessed = "InvoiceProcessed";

    private readonly IServiceProvider _serviceProvider = serviceProvider;

    public async Task HandleEvent(string eventName, object eventData)
    {
        var cancellationToken = new CancellationToken();

        if (eventName == PurchaseOrder)
        {
            IEventHandler handler = _serviceProvider.GetRequiredKeyedService<IEventHandler>(PurchaseOrder);
            await handler.HandleAsync(eventData, cancellationToken);
            return;
        }
        else if (eventName == PaymentProcessed)
        {
            IEventHandler handler = _serviceProvider.GetRequiredKeyedService<IEventHandler>(PaymentProcessed);
            await handler.HandleAsync(eventData, cancellationToken);
            return;
        }
        else if (eventName == InvoiceProcessed)
        {
            IEventHandler handler = _serviceProvider.GetRequiredKeyedService<IEventHandler>(InvoiceProcessed);
            await handler.HandleAsync(eventData, cancellationToken);
            return;
        }
        throw new InvalidOperationException($"No handler found for event: {eventName}");
    }
}

Registro de Injeção de Dependência

serviceCollection.AddKeyedScoped<IEventHandler, PurchaseOrderEventHandler>(EventHandlerFactory.PurchaseOrder);
serviceCollection.AddKeyedScoped<IEventHandler, PaymentEventHandler>(EventHandlerFactory.PaymentProcessed);
serviceCollection.AddKeyedScoped<IEventHandler, InvoiceEventHandler>(EventHandlerFactory.InvoiceProcessed);

Implementação Aprimorada

Perceba que na implementação acima, o eventName que chega no método HandleEvent é exatamente a mesma chave usada para resolver o serviço. Isso significa que podemos eliminar completamente a verificação condicional:

internal class EventHandlerFactory(IKeyedServiceProvider serviceProvider) : IEventHandlerFactory
{
    public async Task HandleEvent(string eventName, object eventData)
    {
        var cancellationToken = new CancellationToken();
        IEventHandler handler = serviceProvider.GetRequiredKeyedService<IEventHandler>(eventName);
        await handler.HandleAsync(eventData, cancellationToken);
    }
}

Considerações Finais

Espero que você tenha aprendido algo novo hoje. Esse conceito era bem novo pra mim até algumas semanas atrás.

Recursos:

Me siga para mais conteúdo sobre .NET, arquitetura de software e práticas de engenharia.

Carregando publicação patrocinada...