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

依存ルール

このドキュメントの目的

クリーンアーキテクチャの核心である依存ルールを深く理解することが目標です。なぜ内側は外側を知ってはいけないのか、その理由を明確にします。


目次

  1. 依存ルールとは
  2. なぜこのルールが必要か
  3. 依存を逆転させる方法
  4. よくある誤解
  5. まとめ

依存ルールとは

シンプルなルール

┌─────────────────────────────────────────────┐
│ 依存ルール │
├─────────────────────────────────────────────┤
│ │
│ 「ソースコードの依存は、 │
│ 常に内側(より抽象的な層)に向かう」 │
│ │
│ 言い換えると: │
│ 「内側の層は外側の層について │
│ 何も知ってはならない」 │
│ │
└─────────────────────────────────────────────┘

具体的には

┌─────────────────────────────────────────────┐
│ 許される依存 │
├─────────────────────────────────────────────┤
│ │
│ Controller → UseCase ✓ │
│ UseCase → Entity ✓ │
│ Repository実装 → Repository ✓ │
│ │
│ 外側が内側を知る = OK │
│ │
└─────────────────────────────────────────────┘

┌─────────────────────────────────────────────┐
│ 許されない依存 │
├─────────────────────────────────────────────┤
│ │
│ Entity → UseCase ✗ │
│ UseCase → Controller ✗ │
│ UseCase → PostgreSQL ✗ │
│ │
│ 内側が外側を知る = NG │
│ │
└─────────────────────────────────────────────┘

「知る」とは

┌─────────────────────────────────────────────┐
│ コードレベルで「知る」とは │
├─────────────────────────────────────────────┤
│ │
│ ・import文がある │
│ ・クラス名/関数名を直接使っている │
│ ・型として参照している │
│ │
│ 内側のファイルに外側のimportがあれば │
│ それは依存ルール違反 │
│ │
└─────────────────────────────────────────────┘

なぜこのルールが必要か

変更の波及を防ぐ

┌─────────────────────────────────────────────┐
│ 依存ルールがない場合 │
├─────────────────────────────────────────────┤
│ │
│ Entity → PostgreSQL │
│ │
│ PostgreSQLの仕様変更 │
│ ↓ 影響 │
│ Entityを修正 │
│ ↓ 影響 │
│ Entityを使う全てのUseCaseを確認 │
│ ↓ 影響 │
│ テスト全体のやり直し │
│ │
│ 技術的詳細の変更がビジネスロジックに波及 │
│ │
└─────────────────────────────────────────────┘

┌─────────────────────────────────────────────┐
│ 依存ルールがある場合 │
├─────────────────────────────────────────────┤
│ │
│ Entity ← PostgreSQL実装 │
│ │
│ PostgreSQLの仕様変更 │
│ ↓ 影響 │
│ PostgreSQL実装のみ修正 │
│ │
│ Entityは無傷、UseCaseも無傷 │
│ │
└─────────────────────────────────────────────┘

変わりやすさと変わりにくさ

┌─────────────────────────────────────────────┐
│ なぜ内側を守るのか │
├─────────────────────────────────────────────┤
│ │
│ 内側(Entity、UseCase): │
│ ├── ビジネスの本質 │
│ ├── 変わりにくい │
│ └── 変わると影響が大きい │
│ │
│ 外側(DB、Framework): │
│ ├── 技術的な選択 │
│ ├── 変わりやすい │
│ └── 変えられるべき │
│ │
│ 変わりやすいものの変更が │
│ 変わりにくいものに影響しない構造を作る │
│ │
└─────────────────────────────────────────────┘

依存を逆転させる方法

問題: UseCaseがDBを使いたい

UseCaseはデータを永続化したい。でもDBを直接知ってはいけない。どうする?

素朴な実装(ルール違反):

UseCase

└── PostgreSQLRepository を直接使う
(UseCaseがPostgreSQLを知っている)

解決: インターフェースを使う

┌─────────────────────────────────────────────┐
│ インターフェースによる依存の逆転 │
├─────────────────────────────────────────────┤
│ │
│ 内側(UseCase層): │
│ ┌─────────────────────┐ │
│ │ UserRepository │ ← インターフェース │
│ │ (interface) │ │
│ │ + save(user) │ │
│ │ + findById(id) │ │
│ └─────────────────────┘ │
│ ↑ │
│ │ 実装 │
│ 外側(インフラ層): │
│ ┌─────────────────────┐ │
│ │ PostgreSQLUser │ │
│ │ Repository │ │
│ │ + save(user) │ │
│ │ + findById(id) │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────┘

依存の方向を確認

依存の方向:

UseCase → UserRepository(interface) ✓ 内側への依存
PostgreSQLUserRepository → UserRepository(interface) ✓ 内側への依存

UseCaseはPostgreSQLを知らない
PostgreSQLがインターフェースを知っている

依存が逆転している!

なぜこれで解決するのか

┌─────────────────────────────────────────────┐
│ インターフェースの効果 │
├─────────────────────────────────────────────┤
│ │
│ UseCaseが知っているのは: │
│ └── 「saveとfindByIdができる何か」 │
│ 具体的な実装は知らない │
│ │
│ PostgreSQL実装が知っているのは: │
│ └── 「このインターフェースを満たす必要がある」│
│ UseCaseは知らない │
│ │
│ お互いがインターフェースだけを知っている │
│ → 実装を自由に差し替えられる │
│ │
└─────────────────────────────────────────────┘

よくある誤解

誤解1: 「層が多いと複雑になる」

┌─────────────────────────────────────────────┐
│ 誤解 │
├─────────────────────────────────────────────┤
│ │
│ 「クリーンアーキテクチャは層が多くて複雑」 │
│ │
│ 実際: │
│ 層の数は問題ではない │
│ 依存の方向が正しいかどうかが重要 │
│ │
│ 2層でも依存が正しければクリーン │
│ 10層でも依存が逆なら意味がない │
│ │
└─────────────────────────────────────────────┘

誤解2: 「インターフェースを作ればOK」

┌─────────────────────────────────────────────┐
│ 誤解 │
├─────────────────────────────────────────────┤
│ │
│ 「インターフェースを作ったから大丈夫」 │
│ │
│ 実際: │
│ インターフェースがどこにあるかが重要 │
│ │
│ ダメな例: │
│ インターフェースが外側(インフラ層)にある │
│ → UseCaseがインフラ層のinterfaceをimport │
│ → 依存ルール違反 │
│ │
│ 良い例: │
│ インターフェースが内側(UseCase層)にある │
│ → 外側がinterfaceを実装 │
│ → 依存ルール遵守 │
│ │
└─────────────────────────────────────────────┘

誤解3: 「データの流れと依存は同じ」

┌─────────────────────────────────────────────┐
│ 誤解 │
├─────────────────────────────────────────────┤
│ │
│ 「データが流れる方向 = 依存の方向」 │
│ │
│ 実際: │
│ データの流れと依存の方向は別物 │
│ │
│ データの流れ: │
│ Controller → UseCase → Repository → DB │
│ DB → Repository → UseCase → Controller │
│ (双方向) │
│ │
│ 依存の方向: │
│ Controller → UseCase → Repository(interface)│
│ RepositoryImpl → Repository(interface) │
│ (常に内側へ) │
│ │
└─────────────────────────────────────────────┘

まとめ

依存ルールの本質

┌─────────────────────────────────────────────┐
│ 一言でいうと │
├─────────────────────────────────────────────┤
│ │
│ 「内側は外側を知らない」 │
│ │
│ これにより: │
│ ├── 外側を変えても内側に影響しない │
│ ├── 内側だけで単独テストできる │
│ └── 技術選択を後から変えられる │
│ │
└─────────────────────────────────────────────┘

実現方法

インターフェースを「内側」に置く
外側がそのインターフェースを実装する
→ 依存の方向が逆転する

次のステップ

  • 境界 でどこに境界を引くべきか学ぶ