第3章:Dependency Rule(依存の向き)が全て⬅️💘
今日の主役はコレ👇 「import の矢印は、内側に向かうだけ」✨ つまり 中心(Entities/UseCases)が、外側(DB/HTTP/フレームワーク)を知らない ってことだよ〜😊💕
Clean Architecture の “The Dependency Rule” は 「ソースコードの依存は内向きにしか向けない」 というルールだよ、って Uncle Bob がはっきり言ってるよ📌 (blog.cleancoder.com)
🎯 到達目標(この章が終わったらできること)
- どの import が OK✅ / NG❌ か、迷わず判断できる😼✨
- NG が出ても、Port(interface) を作って内向きに直せる🔧💖
- 「動いてるのに設計が死んでる…😇」を 早めに発見できる👀
1) そもそも「依存」ってなに?🤔📦
ここで言う依存は、ざっくり言うと👇
import ... from "..."してる- 型として参照してる(
type,interface,implementsなど) new Xxx()してる /extendsしてる- 何かの名前を「知ってしまってる」📛
ポイントはここ👇 TypeScript の “型だけ import” でも、設計の依存としては依存だよ(ソースコード依存だからね)🧠✨ (blog.cleancoder.com)
2) Dependency Rule を「一言で」🌟
内側は外側を知らない。外側が内側に合わせる。⬅️💘

よくある4層で描くと、こんな矢印になるよ👇
- Frameworks & Drivers(外側)➡️ Adapters ➡️ UseCases ➡️ Entities(中心)
- 逆向き(内側 ➡️ 外側)は禁止❌🚫
3) いちばん多い崩壊パターン3つ💥😵💫
❌ パターンA:UseCase が DB ライブラリを直接 import 🗃️
「早く作りたい!」でやりがち😭
// usecases/createTask.ts(NG例)
import { db } from "../frameworks/db"; // ❌ 外側を知ってる!
export async function createTask(title: string) {
await db.task.insert({ title }); // ❌ DB都合が中心に侵入
}
これが何を壊すか👇
- DB変更(SQLite→別DB)で UseCase が壊れる😇
- テストが重くなる(DB必要)🧪💦
- 「中心」が “技術の都合” に支配される🫠
❌ パターンB:UseCase が HTTP の型を知ってる🌐
// usecases/completeTask.ts(NG例)
import type { Request } from "express"; // ❌ フレームワーク依存!
export async function completeTask(req: Request) {
const id = req.params.id; // ❌ HTTP都合
}
❌ パターンC:Entity が外側の便利関数に寄りかかる🧪⚙️
「つい lodash…」「つい dayjs…」で中心が汚れるやつ🥲
4) 直し方は1つだけ(超重要)🔧✨
✅「内側に interface(Port)を置く」→ 外側が実装する
UseCase が欲しいのは「DBそのもの」じゃなくて、能力だよね?👀 例:
- 「Task を保存したい」
- 「Task を取得したい」
だから内側にこう置く👇
// usecases/ports/TaskRepository.ts(OK✅:内側)
import type { Task } from "../../entities/Task";
export interface TaskRepository {
save(task: Task): Promise<void>;
findById(id: string): Promise<Task | null>;
}
そして UseCase は Port だけを見て書く👇
// usecases/CreateTask.ts(OK✅)
import type { TaskRepository } from "./ports/TaskRepository";
import { Task } from "../entities/Task";
export class CreateTask {
constructor(private readonly repo: TaskRepository) {}
async execute(title: string) {
const task = Task.create(title);
await this.repo.save(task);
return task;
}
}
外側(DB側)が Port を実装する👇
// adapters/SqliteTaskRepository.ts(OK✅:外側が内側に依存)
import type { TaskRepository } from "../usecases/ports/TaskRepository";
import type { Task } from "../entities/Task";
import { db } from "../frameworks/sqliteClient"; // 外側は外側を知ってOK
export class SqliteTaskRepository implements TaskRepository {
async save(task: Task): Promise<void> {
await db.task.insert({ id: task.id, title: task.title, completed: task.completed });
}
async findById(id: string): Promise<Task | null> {
const row = await db.task.findById(id);
return row ? Task.rebuild(row.id, row.title, row.completed) : null;
}
}
最後に「組み立て」で注入(依存をつなぐ)🏗️💉 (ここが Composition Root の入口になる!)
// main.ts(つなぐだけ)
const repo = new SqliteTaskRepository();
const createTask = new CreateTask(repo);
5) “呼び出し方向” と “依存方向” は別モノだよ🌀💡
「え、でも UseCase から repo.save() 呼んでるじゃん?外向きじゃないの?😳」 ってなるんだけど…
- 呼び出し(実行時):UseCase → repo.save()(外側の実体が動く)
- 依存(ソースコード):UseCase →
TaskRepository(内側のinterfaceだけ知ってる)
この “ズラし” が 依存逆転(DIPの使いどころ) だよ〜🫶✨ (クリーンアーキ文脈の「依存逆転」は、このテクが中心!) (blog.cleancoder.com)
6) OK✅ / NG❌ 依存 判定表(コピペ用)📋✨
✅ 基本ルール
- 外側 → 内側:OK✅
- 内側 → 外側:NG❌
✅ よくある判定
- Entities が UseCases を import → ❌(中心が外を知る)
- UseCases が Entities を import → ✅
- UseCases が Adapters/Framework を import → ❌
- Adapters が UseCases を import → ✅
- Frameworks が Adapters/UseCases/Entities を import → ✅
✅ “名前が出たら負け”チェック😇
- UseCases に
express,sqlite,prisma,fetch,Reactなどの名前が出た → だいたい境界違反の合図🚨
7) 仕組みで守る(人はミスる😇)🛡️✨
「気をつけます!」は、3日で負ける😂 だから ESLint で止めるのが強いよ💪💕
✅ 代表選手
no-restricted-imports(禁止 import を明示できる) (eslint.org)eslint-plugin-boundaries(レイヤー境界をルール化しやすい) (npm)- Nx を使うなら
@nx/enforce-module-boundaries(大規模でも強い) (Nx)
「UseCases から frameworks/ を import したらエラー」みたいに、ゲームのルール化しちゃおう🎮🚫✨
8) 今日のミニ演習🧠📝
🧩 演習1:違反を見つけよう👀
次の import、OK/NGどっち?(理由も!)
adapters/Controller.ts→usecases/CreateTask.tsusecases/CreateTask.ts→frameworks/sqliteClient.tsframeworks/server.ts→adapters/Controller.ts
(答え:1✅ 2❌ 3✅)
🛠️ 演習2:直してみよう(王道ルート)🔧
NGな UseCase があったら、次の順で直す✨
- UseCase が欲しい “能力” を言語化(保存?取得?通知?)🗣️
- 内側に
interface(Port)を置く🧩 - UseCase は Port だけを見る👀
- 外側で実装(Adapter)して満たす🧰
- 組み立てで注入(DI)💉
🤖 AI相棒プロンプト(コピペOK)✨
依存違反チェック
次のTypeScriptコードのimport依存を確認して、Clean ArchitectureのDependency Rule違反(内側→外側)を列挙して。
違反ごとに「どうPort(interface)を切れば直るか」も提案して、修正後のファイル構成案も出して。
修正案を “差分” で
このUseCaseがframeworksをimportしてしまっている。Port(interface)導入でDependency Ruleを守る形に直したい。
修正案をgit diff形式で出して(新規ファイル作成もOK)。UseCaseのpublic APIはなるべく変えないで。
✅ 理解チェック問題(1問)🧪
質問:
UseCase から DB 実装クラスを import せずに repo.save() を呼べるのは、なぜ?🤔
答えのキーワード: 「依存は interface(Port)へ」「実体は外側」「組み立てで注入」💉✨
📦 提出物(成果物)🎁
- OK/NG依存の判定表(この章の表を自分のプロジェクト構成に合わせて調整)📋
- “NGなUseCase” を1つ選んで、Port導入で修正したPR(または差分)🛠️
🔎 2026/01/22 時点の「最新」メモ(軽く)🧷✨
TypeScript の安定版は npm 上で 5.9.3 が latest として表示されてるよ📦 (npm) それとは別に、TypeScript のコンパイラ/ツールをネイティブ化する大きい動き(TypeScript 7 “native” preview など)も進捗が出てるよ🚀 (Microsoft Developer)
まとめ🎀✨
- Dependency Rule = import の矢印は内側へ⬅️💘
- 内側が外側を知った瞬間、変更とテストが地獄になる😇
- 直し方は Port(interface)を内側に置く だけ🧩✨
- ESLint で “ルール化” すると強い🛡️ (eslint.org)
次の章(第4章:Boundary🚧)に進むと、「じゃあ境界で何を変換するの?」がスルスル分かるようになるよ〜😊💞