【図解で理解】クリーンアーキテクチャとDI(依存性注入)を理解する
クリーンアーキテクチャを勉強していて DI(Dependency Injection:依存性注入) の理解がめちゃめちゃ難しく理解しにくかったので、この記事では、TypeScriptのコード例付きで
「どの部分がDIなのか」もコメントで分かるように備忘録として残しました。
💡 そもそもDIってなに?
一言で言うと…
「中の処理が外の仕組み(DBやAPI)を直接知らないようにする」こと。
🧰 たとえ話
👷♂️「工事の人(=ユースケース)」が
🔌「電動ドライバー(=DBやAPI)」を直接持って作業しているとします。
でも、
- 工具が壊れたら全部直さなきゃいけない
- 新しい工具に変えるたびにコードを全部直す
これ、めちゃくちゃ効率悪いですよね。
そこでこうします👇
「工事人は“ドライバーの渡し方”だけ決めておいて、どのドライバーを渡すかは外で決める」
これが 依存性注入(DI) です。
⚙️ 実際のコードで見てみよう!
🧩 1. 抽象(どんな機能が必要か)を定義する
// core/ports/TodoRepository.ts
export interface TodoRepository {
add(title: string): Promise<void>;
list(): Promise<string[]>;
}
🧠 2. ユースケース(ビジネスロジック)
// core/usecases/AddTodoUseCase.ts
import { TodoRepository } from "../ports/TodoRepository";
export class AddTodoUseCase {
constructor(private repo: TodoRepository) {} // 🟢 ← ここがDIを受け取る「入口」
async execute(title: string) {
if (!title.trim()) throw new Error("Empty title");
await this.repo.add(title); // repoが何かは知らない
}
}
✅ このクラスは「TodoRepositoryがあれば動く」という依存の“契約”だけを持っています。
つまり、「道具の使い方」しか知らない。
// infra/prisma/PrismaTodoRepository.ts
import { TodoRepository } from "../../core/ports/TodoRepository";
export class PrismaTodoRepository implements TodoRepository {
async add(title: string) {
console.log("PrismaでDBに保存:", title);
}
async list() {
return ["勉強する", "牛乳を買う"];
}
}
→ Prismaを使って実際に保存しています。
でも AddTodoUseCase はこのクラスを知らない。
知っているのは「TodoRepositoryという契約(interface)」だけです。
💉 4. 最後に「どの実装を使うか」を注入(DI)する
// app/api/todo.ts (Composition Root)
import { AddTodoUseCase } from "@/core/usecases/AddTodoUseCase";
import { PrismaTodoRepository } from "@/infra/prisma/PrismaTodoRepository";
// 🟢 ここが「依存性注入」本体!
const repo = new PrismaTodoRepository(); // 使う道具(依存)を作る
const usecase = new AddTodoUseCase(repo); // ユースケースに注入する!
await usecase.execute("勉強する");
✅ DIを使うメリット
| シーン | DIなし | DIあり |
|---|---|---|
| DBを変えたい | 全部書き直し | 差し替えるだけ |
| テストしたい | 実DBが必要 | モックに変えるだけ |
| 責任範囲 | ごちゃ混ぜ | はっきり分離 |
| 影響範囲 | 広い | 狭い |
✨ まとめ
・DIは “依存を外から注入する” 仕組み
・目的は 「内側のロジックを外部の実装から独立させる」
・結果として テストしやすく・修正に強く・長持ちする
・クリーンアーキテクチャでは 依存の方向を統一するための重要パーツ