メインコンテンツまでスキップ

第60章:依存性注入(DI)の真の目的💉✨

依存性注入(DI)の真の目的

テスト時に「本物のDB」を使わなくて済むようにする🧪🗄️


今日のゴール🎯

この章が終わると、次の状態になれます😊

  • 「DIって結局なにが嬉しいの?」にスパッと答えられる💡
  • DBなしでユースケースをテストできるようになる🧪✨
  • そして、AI(Copilot/Codex)に頼んでも設計が崩れにくくなる🚀

1. DIなしだと、なにがつらいの?😇(あるある地獄)

たとえば「ユーザー登録」を作るとします。

❌よくある“直書き”パターン

  • アプリの処理の中で DBに直接つなぐ

  • そのせいでテストしたいのに…

  • DBが必要😵

  • 接続文字列が必要😵

  • テーブル作成が必要😵

  • テストが遅い😵

  • テストデータ掃除が面倒😵

つまり… 「ユースケースのテスト」なのに「DBの準備」で詰むんです🫠


2. 依存(Dependency)ってなに?🧩

「ユーザー登録」の処理が、DBアクセスの部品に頼ってる(=依存してる)状態。

  • ユースケース(主役)🎬
  • DBアクセス(脇役)🧰

主役が脇役にベッタリだと、脇役が用意できないと何もできない…ってなります😢


3. 発想を逆にする💡「newしないで、外から渡す」

DIのコアはこれだけです👇

必要な部品を、自分で new しない 外から渡してもらう(注入する)💉

.NET にはDIがフレームワークの一部として組み込まれていて、設定やログ等とも一緒に使える前提になっています。(Microsoft Learn)


4. “DBそのもの”じゃなくて“約束(インターフェース)”に依存する🤝

DDDの流れだと、ここで出てくるのが Repository でしたね🙂 DIのために、まず 「保存する」約束 を作ります。

✅Domain / Application 側(約束だけ持つ)

public interface IUserRepository
{
Task AddAsync(User user, CancellationToken ct);
Task<User?> FindByEmailAsync(Email email, CancellationToken ct);
}

ポイントはここ✨

  • Applicationは IUserRepository(約束) だけ知ってる
  • 「EF Core?SQL?知らん!」でOK🙆‍♀️

5. これが本題🔥 テスト用に“偽物DB”を差し込める🧪✨

本物DBを使わないために、テストではこうします👇

✅テスト用 InMemory 実装(偽物)

public sealed class InMemoryUserRepository : IUserRepository
{
private readonly List<User> _users = new();

public Task AddAsync(User user, CancellationToken ct)
{
_users.Add(user);
return Task.CompletedTask;
}

public Task<User?> FindByEmailAsync(Email email, CancellationToken ct)
{
var user = _users.FirstOrDefault(x => x.Email == email);
return Task.FromResult(user);
}
}

これでテストは…

  • DB不要😍
  • 超高速😍
  • 失敗原因が分かりやすい😍

ユースケースのロジックだけを安心してテストできます🧪✨


6. ユースケース(Applicationサービス)をDIできる形にする🧠

「ユーザー登録」ユースケース例です👇

public sealed class RegisterUserUseCase
{
private readonly IUserRepository _userRepository;

public RegisterUserUseCase(IUserRepository userRepository)
{
_userRepository = userRepository;
}

public async Task<Result> ExecuteAsync(string emailRaw, CancellationToken ct)
{
var email = Email.Create(emailRaw);
if (email.IsFailure) return email.Error!;

var exists = await _userRepository.FindByEmailAsync(email.Value!, ct);
if (exists is not null) return Result.Failure("このメールアドレスは既に登録されています");

var user = User.Create(email.Value!);
await _userRepository.AddAsync(user, ct);

return Result.Success();
}
}

ここが気持ちいいところ😊

  • RegisterUserUseCaseDBを知らない
  • Repositoryを差し替えるだけで、テストも本番も回る♻️

7. .NETのDIコンテナに「本番の部品」を登録する🧰

本番では当然、EF CoreなどのRepository実装を使いたいですよね🙂 そこで .NET標準DI に登録します。

✅ASP.NET Coreの例(Program.cs)

var builder = WebApplication.CreateBuilder(args);

// 例:本番用の実装を登録
builder.Services.AddScoped<IUserRepository, EfUserRepository>();

var app = builder.Build();
app.Run();

AddScoped / AddTransient / AddSingleton などのライフタイムがあり、.NETの公式ドキュメントでも基本として整理されています。(Microsoft Learn)

ざっくり感覚はこれ👇(超入門版)

  • AddTransient:毎回new(軽い部品向き)
  • AddScoped:リクエスト中は同じ(DBアクセス系でよく使う)
  • AddSingleton:ずっと同じ(設定・キャッシュ系)

8. じゃあ「DIの真の目的」って結局なに?💡(答え)

ここまでの話を一言にすると…

✅DIの真の目的

**「差し替え可能にして、テストをラクにする」**🧪✨

特に強いのがこれ👇

  • “本物DB”を使わないテストができる
  • だから 速い・安定・原因が分かる
  • そして 変更に強い(DBや外部APIが変わっても、約束さえ守ればOK)

9. やりすぎ注意⚠️(DIで迷子になるパターン)

DIは便利なんですが、初心者がハマりがち😇

❌全部インターフェースにする

  • 変更しない・差し替えない部品まで抽象化すると “読むコスト”が増えて逆に遅くなります💦

✅目安(1人開発向け)

  • 外部に触るもの(DB / HTTP / ファイル / 時刻 / 乱数 / メール送信) → 抽象化してDIする価値が高い💎
  • 純粋な計算(副作用なし) → そのままでOK🙆‍♀️

10. AI(Copilot/Codex)に頼むときのコツ🤖✨

AIはDIの“型”を作らせると超強いです💪

✅おすすめプロンプト(そのまま投げてOK)

  • IUserRepository を定義して、InMemory実装も作って。スレッドセーフは不要。メソッドは Add と FindByEmail。」
  • RegisterUserUseCase をDI前提(コンストラクタ注入)で書いて。例外は投げず Result で返して。」
  • 「xUnitで、DBなしで通るテストを書いて。既存メールは失敗になるケースも。」

コツは“設計の骨格(境界)を人間が決める”こと🦴✨ ここが決まってると、AIが大量に書いても崩れにくいです😊


11. ミニ演習🧪✨「DBなしでユーザー登録をテストする」

やることは3つだけ!

Step1:InMemoryUserRepository を用意(上のコードでOK)

Step2:UseCase を new するときに注入する

var repo = new InMemoryUserRepository();
var useCase = new RegisterUserUseCase(repo);

Step3:xUnitでテスト(例)

using Xunit;

public class RegisterUserUseCaseTests
{
[Fact]
public async Task 新規メールなら登録成功する()
{
var repo = new InMemoryUserRepository();
var useCase = new RegisterUserUseCase(repo);

var result = await useCase.ExecuteAsync("test@example.com", CancellationToken.None);

Assert.True(result.IsSuccess);
}

[Fact]
public async Task 既存メールなら失敗する()
{
var repo = new InMemoryUserRepository();
var useCase = new RegisterUserUseCase(repo);

await useCase.ExecuteAsync("test@example.com", CancellationToken.None);
var result2 = await useCase.ExecuteAsync("test@example.com", CancellationToken.None);

Assert.True(result2.IsFailure);
}
}

これができたら、あなたはもう **「DBに振り回されない設計」**の入り口に立ってます🚪✨


まとめ📌🎉

  • DIは「カッコいい技術」じゃなくて、差し替えのための仕組み💉
  • 真のご褒美は テストで本物DBを使わなくていいこと🧪🗄️
  • だから、開発が速くなって、壊れにくくなる🚀
  • AIにコード生成を任せても、境界が守れて事故りにくい🤖✨

次の章(第61章)では、このDIと相性バツグンな 「アプリケーションサービス(ユースケース層)」 を、もう少しキレイに整理していきますよ〜😊📚