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:
- Repositório POC no GitHub: https://github.com/Raffael-Eloi/keyed-services-poc
- GitHub do Autor: https://github.com/Raffael-Eloi
- LinkedIn: https://linkedin.com/in/raffael-eloi
- Site Pessoal: https://raffaeleloi.dev
Me siga para mais conteúdo sobre .NET, arquitetura de software e práticas de engenharia.