O que Padrões de Injeção de Dependência faz?
Quando Usar Este Skill
Use este skill quando:
- Organizando registros de serviços em aplicações ASP.NET Core
- Evitando arquivos Program.cs/Startup.cs enormes com centenas de registros
- Tornando a configuração de serviços reutilizável entre produção e testes
- Projetando bibliotecas que integram com Microsoft.Extensions.DependencyInjection
Arquivos de Referência
- advanced-patterns.md: Testes com extensões de DI, gerenciamento de escopo de atores Akka.NET, padrões condicionais/factory/chaveados de registro
O Problema
Sem organização, Program.cs torna-se incontrolável:
// RUIM: Mais de 200 linhas de registros desorganizados
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddScoped<IUserService, UserService>();
// ... mais 150 linhas ...
Problemas: difícil encontrar registros relacionados, sem limites claros, não reutilizável em testes, conflitos de merge.
A Solução: Composição de Métodos de Extensão
Agrupe registros relacionados em métodos de extensão:
// BOM: Program.cs limpo e componível
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddUserServices()
.AddOrderServices()
.AddEmailServices()
.AddPaymentServices()
.AddValidators();
var app = builder.Build();
Padrão de Método de Extensão
Estrutura Básica
namespace MyApp.Users;
public static class UserServiceCollectionExtensions
{
public static IServiceCollection AddUserServices(this IServiceCollection services)
{
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IUserReadStore, UserReadStore>();
services.AddScoped<IUserWriteStore, UserWriteStore>();
services.AddScoped<IUserService, UserService>();
services.AddScoped<IUserValidationService, UserValidationService>();
return services;
}
}
Com Configuração
namespace MyApp.Email;
public static class EmailServiceCollectionExtensions
{
public static IServiceCollection AddEmailServices(
this IServiceCollection services,
string configSectionName = "EmailSettings")
{
services.AddOptions<EmailOptions>()
.BindConfiguration(configSectionName)
.ValidateDataAnnotations()
.ValidateOnStart();
services.AddSingleton<IMjmlTemplateRenderer, MjmlTemplateRenderer>();
services.AddSingleton<IEmailLinkGenerator, EmailLinkGenerator>();
services.AddScoped<IUserEmailComposer, UserEmailComposer>();
services.AddScoped<IEmailSender, SmtpEmailSender>();
return services;
}
}
Organização de Arquivos
Coloque métodos de extensão perto dos serviços que eles registram:
src/
MyApp.Api/
Program.cs # Compõe todos os métodos Add*
MyApp.Users/
Services/
UserService.cs
UserServiceCollectionExtensions.cs # AddUserServices()
MyApp.Orders/
OrderServiceCollectionExtensions.cs # AddOrderServices()
MyApp.Email/
EmailServiceCollectionExtensions.cs # AddEmailServices()
Convenção: {Funcionalidade}ServiceCollectionExtensions.cs próximo aos serviços da funcionalidade.
Convenções de Nomenclatura
| Padrão | Uso |
|---|---|
Add{Feature}Services() | Registro geral de funcionalidade |
Add{Feature}() | Forma curta quando não ambíguo |
Configure{Feature}() | Quando define principalmente opções |
Use{Feature}() | Middleware (em IApplicationBuilder) |
Benefícios para Testes
O padrão Add* permite reutilizar a configuração de produção em testes e sobrescrever apenas o que é diferente. Funciona com WebApplicationFactory, Akka.Hosting.TestKit e ServiceCollection autônomo.
Veja advanced-patterns.md para exemplos completos de teste.
Extensões em Camadas
Para aplicações maiores, componha extensões hierarquicamente:
public static class AppServiceCollectionExtensions
{
public static IServiceCollection AddAppServices(this IServiceCollection services)
{
return services
.AddDomainServices()
.AddInfrastructureServices()
.AddApiServices();
}
}
public static class DomainServiceCollectionExtensions
{
public static IServiceCollection AddDomainServices(this IServiceCollection services)
{
return services
.AddUserServices()
.AddOrderServices()
.AddProductServices();
}
}
Integração com Akka.Hosting
O mesmo padrão funciona para configuração de atores Akka.NET:
public static class OrderActorExtensions
{
public static AkkaConfigurationBuilder AddOrderActors(
this AkkaConfigurationBuilder builder)
{
return builder
.WithActors((system, registry, resolver) =>
{
var orderProps = resolver.Props<OrderActor>();
var orderRef = system.ActorOf(orderProps, "orders");
registry.Register<OrderActor>(orderRef);
});
}
}
// Uso em Program.cs
builder.Services.AddAkka("MySystem", (builder, sp) =>
{
builder
.AddOrderActors()
.AddInventoryActors()
.AddNotificationActors();
});
Veja o skill akka-hosting-actor-patterns para padrões completos de Akka.Hosting.
Anti-Padrões
Não: Registrar Tudo em Program.cs
// RUIM: Program.cs enorme com mais de 200 linhas de registros
Não: Criar Extensões Muito Genéricas
// RUIM: Muito vago, não comunica o que é registrado
public static IServiceCollection AddServices(this IServiceCollection services) { ... }
Não: Esconder Configuração Importante
// RUIM: Configurações enterradas
public static IServiceCollection AddDatabase(this IServiceCollection services)
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer("connection-string-hardcoded")); // Escondido!
}
// BOM: Aceitar configuração explicitamente
public static IServiceCollection AddDatabase(
this IServiceCollection services,
string connectionString)
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
}
Resumo de Melhores Práticas
| Prática | Benefício |
|---|---|
Agrupar serviços relacionados em métodos Add* | Program.cs limpo, limites claros |
| Colocar extensões perto dos serviços que registram | Fácil de encontrar e manter |
Retornar IServiceCollection para encadeamento | API fluente |
| Aceitar parâmetros de configuração | Flexibilidade |
Usar nomenclatura consistente (Add{Feature}Services) | Descoberta |
| Testar reutilizando extensões de produção | Confiança, menos duplicação |
Gerenciamento de Ciclo de Vida
| Ciclo de Vida | Use Quando | Exemplos |
|---|---|---|
| Singleton | Sem estado, thread-safe, caro de criar | Configuração, fábricas HttpClient, caches |
| Scoped | Com estado por requisição, contextos de banco | DbContext, repositórios, contexto de usuário |
| Transient | Leve, com estado, barato de criar | Validadores, ajudantes de curta duração |
// SINGLETON: Serviços sem estado, compartilhados com segurança
services.AddSingleton<IMjmlTemplateRenderer, MjmlTemplateRenderer>();
// SCOPED: Acesso a banco, estado por requisição
services.AddScoped<IUserRepository, UserRepository>();
// TRANSIENT: Barato, de curta duração
services.AddTransient<CreateUserRequestValidator>();
Serviços Scoped exigem um escopo. ASP.NET Core cria um por requisição HTTP. Em serviços em segundo plano e atores, crie escopos manualmente.
Veja advanced-patterns.md para padrões de gerenciamento de escopo de atores.
Erros Comuns
Injetar Scoped em Singleton
// RUIM: Singleton captura serviço scoped - DbContext obsoleto!
public class CacheService // Registrado como Singleton
{
private readonly IUserRepository _repo; // Scoped - capturado na inicialização!
}
// BOM: Injetar IServiceProvider, criar escopo por operação
public class CacheService
{
private readonly IServiceProvider _serviceProvider;
public async Task<User> GetUserAsync(string id)
{
using var scope = _serviceProvider.CreateScope();
var repo = scope.ServiceProvider.GetRequiredService<IUserRepository>();
return await repo.GetByIdAsync(id);
}
}
Sem Escopo em Trabalho em Segundo Plano
// RUIM: Sem escopo para serviços scoped
public class BadBackgroundService : BackgroundService
{
private readonly IOrderService _orderService; // Scoped - lançará exceção!
}
// BOM: Criar escopo para cada unidade de trabalho
public class GoodBackgroundService : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
protected override async Task ExecuteAsync(CancellationToken ct)
{
using var scope = _scopeFactory.CreateScope();
var orderService = scope.ServiceProvider.GetRequiredService<IOrderService>();
// ...
}
}
Recursos
- Microsoft.Extensions.DependencyInjection: https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection
- Akka.Hosting: https://github.com/akkadotnet/Akka.Hosting
- Akka.DependencyInjection: https://getakka.net/articles/actors/dependency-injection.html
- Padrão Options: Veja o skill
microsoft-extensions-configuration