第38章:Adapters層の依存監査(中心を汚さない)🛡️
🎯 到達目標(1文)
Adapters(Controller/Presenter/Repository/Mapper)が“便利だから”で中心(UseCases/Entities)を汚してないか、ルール+自動チェックで即発見できる状態にするよ✅🧹
1) まず超ざっくり:Adaptersって何する場所?🧩
Adaptersは「翻訳係」だよ📚✨
- 外側の入力(HTTP/CLI/UIなど)→ UseCaseが食べやすいRequestに翻訳🍽️
- UseCaseのResponse → 外側(画面/HTTP)に見せやすい形に翻訳🎨
- DB/外部サービス → Portを満たす形でつなぎこむ🔌
✅ 逆に、Adaptersがやっちゃダメなのはこれ👇
- ビジネスルールを決める(=中心の仕事)🚫
- 中心の型に外部ライブラリの都合を混ぜる(例:SQLiteのRow型がPortを侵食)🚫
- 依存の向きを逆転させる(UseCasesがAdaptersをimportしちゃう等)🚫
2) “中心が汚れる”あるある事故 💥😇

次のどれかが起きてたら黄色信号だよ⚠️
🧨事故A:Presenterが「仕様」を決め始める
- 例:画面表示のための整形を超えて「未完了は作成日が古い順に並べるのが正しい仕様!」みたいに決める ➡️ それが“業務ルール”ならUseCaseへ、ただの見た目都合ならPresenterでOK(境界の線引きが大事)🎀
🧨事故B:ControllerがUseCaseを飛び越えてEntityを直接いじる
- 「UseCase呼ぶのめんどい」→
task.complete()をControllerで直呼び ➡️ 中心の手続き(取得→更新→保存→レスポンス)が壊れやすい💔
🧨事故C:PortやUseCaseの入出力に“外部ライブラリ型”が混ざる
TaskRepositoryPort.save(row: SqliteRow)みたいなの ➡️ これやると、中心がDB都合に染まる🧟♂️
3) Adapters依存監査チェックリスト ✅🧾(コピペで使えるよ)
3-1. 依存(import)の監査 👀⬅️
- ✅ UseCases/Entitiesが
src/adapters/**をimportしてない - ✅ Entitiesが
src/usecases/**や外部都合(HTTP/DB)をimportしてない - ✅ Portの型が 業務語彙で、外部ライブラリ型が出てこない
- ✅ Adaptersは「翻訳」だけで、中心のルール判断がない(if文の意味を読む!)🧠
3-2. 責務(ロジック)の監査 🧼
- ✅ Controllerは 受け取る→変換→UseCase呼ぶ の3つだけ🚪
- ✅ Presenterは Response→ViewModel の整形だけ🎨
- ✅ Repository/Mapperは 保存/取得と変換 だけ🔄
- ✅ 例外(DB/ネットワーク)は 技術エラーとして境界で変換され、中心に漏れない⚠️➡️🚧
4) 自動化で“違反したら落とす”を作る 🤖🔧
ここからが第38章のメインだよ〜!🎉 2026の現場だと、ESLint(Flat Config)+ dependency-cruiser + madge の組み合わせがめっちゃ強い💪✨
4-1) ESLintで「禁止import」をガチガチにする 🧱
ESLintは今どき Flat Config(eslint.config.*) が基本だよ🧹 TypeScript向けの推奨セットも公式で案内されてる(typescript-eslintのGetting Started)📘✨ (TypeScript ESLint) ESLint自体も設定ファイルの仕組みを公式ドキュメントで案内してるよ📄 (ESLint)
例:eslint.config.mjs(まずこれでOK)👇
// eslint.config.mjs
// @ts-check
import eslint from "@eslint/js";
import { defineConfig } from "eslint/config";
import tseslint from "typescript-eslint";
export default defineConfig(
eslint.configs.recommended,
tseslint.configs.recommended,
// ✅ Entities:中心の中心。外側を見ない!
{
files: ["src/entities/**/*.{ts,tsx}"],
rules: {
"no-restricted-imports": [
"error",
{
patterns: [
{
group: ["src/usecases/**", "src/adapters/**", "src/frameworks/**"],
message: "Entitiesは外側に依存しないよ🛡️(中心のルールだけ!)",
},
],
},
],
},
},
// ✅ UseCases:Adapters/Frameworksを見ない!(Port経由にする)
{
files: ["src/usecases/**/*.{ts,tsx}"],
rules: {
"no-restricted-imports": [
"error",
{
patterns: [
{
group: ["src/adapters/**", "src/frameworks/**"],
message: "UseCases→外側依存は禁止🚫(Portで受け取ろう🔌)",
},
],
},
],
},
},
// ✅ Adapters:ここは“翻訳係”。中心を呼ぶのはOK、でも中心の判断はしない!
// (必要なら inbound/outbound の相互import禁止なども追加できるよ)
);
💡ワンポイント
tseslint.configs.recommendedを使う構成は公式案内に沿ってるよ📘 (TypeScript ESLint)- ESLintの設定ファイル仕様は公式ドキュメントが基準📄 (ESLint)
🔎 ちなみにESLintは「TypeScriptの設定ファイル(eslint.config.ts/mts/cts)」もサポートを進めてきてるよ(公式ブログ)📰 (ESLint)
(でもまずは .mjs で安定運用がラク✨)
4-2) dependency-cruiserで「アーキ違反」をテスト化する 🧪🚨
dependency-cruiserは 依存ルールを自分で書いて違反を検出できるやつだよ🛡️
depcruise --initで設定ファイルを作れる- ルール例(from/toで禁止)もREADMEに載ってる📌 (GitHub)
📦 インストール&初期化
npm i -D dependency-cruiser
npx depcruise --init
✅ 例:クリーンアーキの“最低限”ルールを追加
.dependency-cruiser.js に forbidden を足すイメージ👇
(READMEのルール形をそのまま使えるよ) (GitHub)
// .dependency-cruiser.js (抜粋イメージ)
module.exports = {
forbidden: [
// UseCasesはAdaptersを見ない
{
name: "no-usecases-to-adapters",
severity: "error",
from: { path: "^src/usecases" },
to: { path: "^src/adapters" },
},
// UseCasesはFrameworksも見ない
{
name: "no-usecases-to-frameworks",
severity: "error",
from: { path: "^src/usecases" },
to: { path: "^src/frameworks" },
},
// Entitiesは外側を見ない(超重要)
{
name: "no-entities-outward",
severity: "error",
from: { path: "^src/entities" },
to: { path: "^src/(usecases|adapters|frameworks)" },
},
],
};
▶ 実行
npx depcruise src
4-3) madgeで「循環依存」をあぶり出す 🌀👻
循環依存って、設計がにごると増えがち😇 madgeは 循環依存を探すのが得意だよ💡(npm公式にも説明あり) (npm)
npx madge --circular --extensions ts,tsx src
5) “Adapters層”の監査ポイントをもっと具体化 💎
ここ、超大事だからもう一段かみくだくね🫶✨
✅ Adaptersに置いていいロジック(OK)🟢
- 変換:
HTTP body → CreateTaskRequest - 整形:
ListTasksResponse → TaskListViewModel - エラー変換:
DomainError → { status, message } - マッピング:
DB row ↔ Task(Entity)
❌ Adaptersに置いちゃダメなロジック(NG)🔴
- 「タイトルは20文字以内が正しい」みたいな業務ルール確定
- 「完了できるのは◯◯の場合だけ」みたいな状態遷移ルール確定
- 「DBがこういう制約だから、中心の型もこうして」みたいな外部都合の押し込み
6) ミニ演習(10分)⌛🎒
🧪 わざと違反して、ツールに怒らせよう😈➡️😇
-
UseCaseからAdapterをimportしてみる(わざと!)
src/usecases/CreateTaskInteractor.tsでsrc/adapters/...をimport
-
ESLintを走らせる
npx eslint .
- 「UseCases→外側依存は禁止🚫」って怒られたら成功🎉
- 修正:Port(interface)をUseCases側に置いて、Adapterはそれを実装する形に戻す🔌✨
7) 提出物(成果物)📦✨
- ✅
eslint.config.mjs(禁止importルール入り) - ✅
.dependency-cruiser.js(Clean Architecture最低限ルール入り) - ✅
package.jsonに監査スクリプト(任意だけどあると最高)
例:
{
"scripts": {
"lint": "eslint .",
"lint:deps": "depcruise src",
"lint:cycles": "madge --circular --extensions ts,tsx src"
}
}
8) AI相棒🤖💬(コピペ用プロンプト)
🩺 Adapter診断
次のファイルはAdapterです。中心(Entities/UseCases)に責務が漏れてないか診断して、
(1)怪しい行
(2)なぜNGか
(3)修正案(どの層に移すべきか)
を箇条書きで出して。
🧱 依存ルール生成(dependency-cruiser)
このフォルダ構成でClean Architectureの依存ルールをdependency-cruiserのforbiddenで作って。
最低限:Entitiesは外側を見ない / UseCasesはAdaptersとFrameworksを見ない。
必要なら次は、第38章の内容に合わせて 「✅ 監査に落ちたときの“直し方パターン集”(典型10個)」も作れるよ📚✨ (“Controllerがnewしてる問題”とか、“Portに外部型が漏れてる問題”とかを、テンプレで直せるやつ💪😆)