dependency-injection-patterns について
ASP.NET Core 開発者向けの依存性注入パターンをまとめたスキルです。拡張メソッドによる登録整理、本番設定のテストでの再利用、サービスのライフタイムとスコープ管理を扱います。
主な機能
- 01機能ごとのサービス登録整理
- 02連鎖可能な拡張メソッド
- 03階層的な登録構成
- 04本番設定のテストでの再利用
- 05サービスのライフタイムとスコープ管理
活用シーン
- 01大規模な Program.cs の登録整理
- 02テストでのサービス設定再利用
- 03設定可能なサービスライブラリの構築
- 04バックグラウンド処理のスコープ管理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ aaronontheweb/ dotnet-skills --skill dependency-injection-patternsインストール後、Claude Code で/dependency-injection-patternsを入力して読み込まれたか確認します。
同梱ファイルadvanced-patterns.md
詳細
依存性注入のパターン
このスキルを使用する場面
以下の場合にこのスキルを使用してください。
- ASP.NET Core アプリケーションでサービス登録を整理するとき
- 数百件の登録で
Program.cs/Startup.csが肥大化するのを防ぐとき - 本番環境とテストでサービス構成を再利用できるようにするとき
- Microsoft.Extensions.DependencyInjection と連携するライブラリを設計するとき
参照ファイル
- advanced-patterns.md:DI 拡張機能を使ったテスト、Akka.NET アクターのスコープ管理、条件付き/ファクトリー/キー付き登録のパターン
問題点
整理しないと、Program.cs が管理しきれない状態になります。
// BAD: 200+ lines of unorganized registrations
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>();
// ... 150 more lines ...
問題点:関連する登録を見つけにくく、境界が明確でなく、テストで再利用できず、マージ時の競合が発生します。
解決策:拡張メソッドの構成
関連する登録を拡張メソッドにまとめます。
// GOOD: Clean, composable Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddUserServices()
.AddOrderServices()
.AddEmailServices()
.AddPaymentServices()
.AddValidators();
var app = builder.Build();
拡張メソッドのパターン
基本構造
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;
}
}
構成を伴う場合
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;
}
}
ファイル構成
拡張メソッドは、登録するサービスの近くに配置します。
src/
MyApp.Api/
Program.cs # Composes all Add* methods
MyApp.Users/
Services/
UserService.cs
UserServiceCollectionExtensions.cs # AddUserServices()
MyApp.Orders/
OrderServiceCollectionExtensions.cs # AddOrderServices()
MyApp.Email/
EmailServiceCollectionExtensions.cs # AddEmailServices()
規約:{Feature}ServiceCollectionExtensions.cs を、機能のサービスと同じ場所に配置します。
命名規則
| パターン | 用途 |
|---|---|
Add{Feature}Services() | 一般的な機能の登録 |
Add{Feature}() | 曖昧さがない場合の短縮形 |
Configure{Feature}() | 主にオプションを設定する場合 |
Use{Feature}() | ミドルウェア(IApplicationBuilder 上) |
テストでのメリット
Add* パターンを使うと、本番環境の構成をテストで再利用し、異なる部分だけをオーバーライドできます。WebApplicationFactory、Akka.Hosting.TestKit、スタンドアロンの ServiceCollection で使用できます。
テストの完全な例については、advanced-patterns.md を参照してください。
階層化された拡張機能
大規模なアプリケーションでは、拡張機能を階層的に構成します。
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();
}
}
Akka.Hosting との統合
同じパターンを 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);
});
}
}
// Usage in Program.cs
builder.Services.AddAkka("MySystem", (builder, sp) =>
{
builder
.AddOrderActors()
.AddInventoryActors()
.AddNotificationActors();
});
Akka.Hosting のパターン全般については、akka-hosting-actor-patterns スキルを参照してください。
アンチパターン
避ける:すべてを Program.cs に登録する
// BAD: Massive Program.cs with 200+ lines of registrations
避ける:汎用的すぎる拡張機能を作成する
// BAD: Too vague, doesn't communicate what's registered
public static IServiceCollection AddServices(this IServiceCollection services) { ... }
避ける:重要な構成を隠す
// BAD: Buried settings
public static IServiceCollection AddDatabase(this IServiceCollection services)
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer("hardcoded-connection-string")); // Hidden!
}
// GOOD: Accept configuration explicitly
public static IServiceCollection AddDatabase(
this IServiceCollection services,
string connectionString)
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
}
ベストプラクティスのまとめ
| プラクティス | メリット |
|---|---|
関連するサービスを Add* メソッドにまとめる | Program.cs がすっきりし、境界が明確になります |
| 拡張機能を登録するサービスの近くに配置する | 見つけやすく、保守しやすくなります |
チェーン呼び出しのために IServiceCollection を返す | 流暢な API を実現できます |
| 構成パラメーターを受け取る | 柔軟性が高まります |
一貫した命名(Add{Feature}Services)を使用する | 見つけやすくなります |
| 本番環境の拡張機能を再利用してテストする | 信頼性が高まり、重複が減ります |
ライフタイム管理
| ライフタイム | 使用する場面 | 例 |
|---|---|---|
| Singleton | ステートレスでスレッドセーフ、生成コストが高い場合 | 構成、HttpClient ファクトリー、キャッシュ |
| Scoped | リクエストごとに状態を持つ場合、データベースコンテキスト | DbContext、リポジトリ、ユーザーコンテキスト |
| Transient | 軽量で状態を持ち、生成コストが低い場合 | バリデーター、短命なヘルパー |
// SINGLETON: Stateless services, shared safely
services.AddSingleton<IMjmlTemplateRenderer, MjmlTemplateRenderer>();
// SCOPED: Database access, per-request state
services.AddScoped<IUserRepository, UserRepository>();
// TRANSIENT: Cheap, short-lived
services.AddTransient<CreateUserRequestValidator>();
Scoped サービスにはスコープが必要です。 ASP.NET Core は HTTP リクエストごとにスコープを作成します。バックグラウンドサービスやアクターでは、スコープを手動で作成します。
アクターのスコープ管理パターンについては、advanced-patterns.md を参照してください。
よくある間違い
Scoped を Singleton に注入する
// BAD: Singleton captures scoped service - stale DbContext!
public class CacheService // Registered as Singleton
{
private readonly IUserRepository _repo; // Scoped - captured at startup!
}
// GOOD: Inject IServiceProvider, create scope per operation
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);
}
}
バックグラウンド処理でスコープを作成しない
// BAD: No scope for scoped services
public class BadBackgroundService : BackgroundService
{
private readonly IOrderService _orderService; // Scoped - will throw!
}
// GOOD: Create scope for each unit of work
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>();
// ...
}
}
リソース
- 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
- Options Pattern:
microsoft-extensions-configurationスキルを参照