第5章 迷わない層モデル:Domain / Application / Adapters を決めよう🧅🗺️
「このファイル、どこ置けばいいの…?😵💫」ってなる瞬間、あるよね。 この章はそれを**ほぼ迷わなくするための“置き場所ルール”**を作る回だよ〜!🧡
1) この章のゴール🎯✨
読み終わったら、こうなれるのが目標だよ👇
- 「これは Domain?Application?Adapters?🤔」を3秒で判定できる⏱️✨
- 置き場所に迷ったとき、判断の質問セットで決められる🧠💡
- “名前”じゃなくて、依存の向き➡️で層が決まる感覚がつかめる🧭
2) まずは一言で覚えよっ📌💕
層モデルって、難しそうに見えるけど、実はコレだけ👇
-
Domain = ルール 🏛️⚖️
- “業務の決まり”とか“絶対こうあるべき”の世界
-
Application = 手順 🧾🧑🍳
- ルールを使って「どう進める?」を組み立てる世界
-
Adapters = 外と話す 🌍🔌
- DB / HTTP / UI / 外部APIなど、外界との会話係
そして一番大事なのがコレ👇
依存の向きは「外 → 内」➡️🧅
(外) (手順) (ルール)
```mermaid
graph RL
Adapters[Adapters<br/>外側] --> App[Application<br/>手順]
App --> Domain[Domain<br/>ルール]
style Domain fill:#e6fffa,stroke:#00b894
style App fill:#fff5e6,stroke:#ffb86c
style Adapters fill:#fff5f5,stroke:#ff7675
「Domainが外部ライブラリに引っ張られてブレる」事故を防ぐために、内側を守る🛡️のがDependency Ruleのノリだよ〜!
3) 具体例:ToDoアプリで“置き場所”を固める📝✨
ここでは超ミニのToDoを想像するよ😊 やることはこんな感じ👇
- ToDoを追加する➕
- 一覧を見る📋
- 完了にする✅
A. Domain(ルール)って何を置くの?🏛️💕
**Domainに置くのは「業務として守りたい決まり」**だよ。
たとえばToDoなら…
- タイトルは空じゃダメ🙅♀️
- 完了済みにしたら、もう未完了には戻せない(仕様なら)🔒
- ToDoの形(Entity)や、IDの型(Value Object)🎁
// src/domain/todo/Todo.ts
export type TodoId = string;
export class Todo {
constructor(
public readonly id: TodoId,
public readonly title: string,
public readonly isDone: boolean,
) {
if (title.trim().length === 0) {
throw new Error("Title must not be empty"); // ※後でドメインエラーにするのが理想✨
}
}
complete(): Todo {
if (this.isDone) return this;
return new Todo(this.id, this.title, true);
}
}
✅ ポイント Domainは「ルール」だから、ここに HTTP とか DB の都合が入ってくるとブレるの…😢 なので Domain はなるべく 素のTypeScriptだけで完結させるイメージだよ🧼✨
B. Application(手順)って何を置くの?🧾🧡
Applicationは「ルールを使って、処理の流れを作る」係!🧑🍳✨ よく UseCase(ユースケース) って呼ばれるよ。
- 「ToDoを追加する」手順
- 「一覧表示する」手順
- 「完了にする」手順
さらにここが超重要👇
✅ Applicationは “外界の能力” を interface で要求する(=契約/ポート)📜✨ DBに保存したいなら「保存できる人だれかお願い〜🙏」って、能力だけ約束する感じ。
// src/app/ports/TodoRepository.ts
import { Todo, TodoId } from "../../domain/todo/Todo";
export interface TodoRepository {
save(todo: Todo): Promise<void>;
findById(id: TodoId): Promise<Todo | null>;
list(): Promise<Todo[]>;
}
そしてUseCase(手順)👇
// src/app/usecases/AddTodo.ts
import { Todo } from "../../domain/todo/Todo";
import { TodoRepository } from "../ports/TodoRepository";
export class AddTodo {
constructor(
private readonly repo: TodoRepository,
private readonly newId: () => string, // 例:ID生成も“外の能力”として注入✨
) {}
async execute(title: string): Promise<{ id: string }> {
const todo = new Todo(this.newId(), title, false);
await this.repo.save(todo);
return { id: todo.id };
}
}
✅ ポイント Applicationは「手順」だから、HTTPステータスやSQLは知らないでOK🙆♀️ 「成功/失敗」や「結果」だけを返すのがスッキリするよ〜✨
C. Adapters(外と話す)って何を置くの?🌍🔌
Adaptersは、Applicationの「お願い(interface)」を実装したり、HTTPとやり取りしたりする層だよ📮✨
- DB実装(例:SQLite / PostgreSQL / Prisma etc)🗄️
- APIルート(HTTPハンドラ)🌐
- 外部サービス(決済、メール、SNS)📩
例:インメモリ実装(テストや開発用)👇
// src/adapters/repositories/InMemoryTodoRepository.ts
import { Todo, TodoId } from "../../domain/todo/Todo";
import { TodoRepository } from "../../app/ports/TodoRepository";
export class InMemoryTodoRepository implements TodoRepository {
private readonly map = new Map<TodoId, Todo>();
async save(todo: Todo): Promise<void> {
this.map.set(todo.id, todo);
}
async findById(id: TodoId): Promise<Todo | null> {
return this.map.get(id) ?? null;
}
async list(): Promise<Todo[]> {
return [...this.map.values()];
}
}
✅ ポイント Adaptersは「外部の都合が全部ある場所」だから、 DTO変換とか、HTTPの事情とか、DBの事情とか、汚れ役を引き受ける場所だよ🧤✨
4) 迷ったときの“判定3問”🧠💡(これでほぼ決まる!)

置き場所に迷ったら、この3つを自分に質問してね👇
Q1. それって「業務のルール」?⚖️
- Yes → Domain 🏛️
- No → 次へ
Q2. それって「手順の組み立て」?🧾
- Yes → Application 🧡
- No → 次へ
Q3. それって「外(DB/HTTP/UI/外部API)と話す」?🌍
- Yes → Adapters 🔌
迷いがちな例も置いとくね👇
-
入力チェック(タイトル空など)
- 業務ルールならDomain(例:タイトル空NG)
- HTTP都合のバリデーションならAdapters(例:リクエスト必須項目チェック)
-
ID生成、現在時刻
- Domainに直書きすると“外の都合”になりがち😵
- Applicationが「関数/ポート」で受け取るのが扱いやすい✨
-
DTO変換(API型↔ドメイン型)
- 基本は Adapters(境界で翻訳する)🔁✨
5) フォルダ構成テンプレ(この章の結論マップ🗺️🧅)

src/
domain/
todo/
Todo.ts
app/
ports/
TodoRepository.ts
usecases/
AddTodo.ts
adapters/
repositories/
InMemoryTodoRepository.ts
http/
http/
routes.ts
「名前は好みでOK」なんだけど、この3分割が見えると迷いが激減するよ😊✨
6) ミニ演習:仕分けクイズ〜!📝🎀
次の“モノ”はどこに置く?(Domain / Application / Adapters)👇
Todoクラス(title空NG、complete()あり)AddTodo.execute(title)(repo.saveして結果返す)POST /todosのHTTPハンドラPrismaTodoRepository(PrismaでDB保存)TodoRepositoryinterfacereq.body.titleが無いとき 400 を返す処理
解答✅✨
- 1 → Domain(ルールと状態)🏛️
- 2 → Application(手順)🧡
- 3 → Adapters(HTTPと会話)🌐
- 4 → Adapters(DBと会話)🗄️
- 5 → Application(外へ要求する能力=契約)📜
- 6 → Adapters(HTTP都合のエラー)🧯
7) よくある事故パターン😱💥(これだけ避ければ勝ち!)
❌ Domainがaxios/prisma/importしてる
- ルールが外部都合に汚染される😵
- 変更に弱くなる
❌ ApplicationがHTTPレスポンスを返す
- 手順がHTTPに縛られて、CLIやバッチに転用しにくい🥲
❌ Adaptersに業務ルールが混入
- 「どこが正しいルール?」って迷子になる🌀
8) AIの使い方🤖✨(この章と相性バツグン!)
そのままコピペで使えるやつ置いとくね🪄💕
① 仕分け相談(迷いゼロ化)🧠🧁
以下のクラス/関数を Domain / Application / Adapters に分類して。
それぞれ「理由」と「依存の向きが正しいか」も説明して。
- (ここにコード or ファイル一覧)
② 置き場所を直す提案🔧✨
この構成で依存の向きが崩れている箇所を指摘して、
修正方針(移動先、interface化、変換の置き場所)を提案して。
可能なら変更差分も出して。
③ 命名案(迷いを減らす魔法)📛🌸
この責務に合う命名を10個出して。
Domainっぽい名前、Applicationっぽい名前、Adapterっぽい名前に分けて。
9) 章末チェックリスト✅💕(保存版!)
- DomainにHTTP/DB/外部SDKの都合が入り込んでない?🏛️🚫
- Applicationは「手順」だけで、外部詳細を知らない?🧡
- Adaptersが翻訳・接続の役割をちゃんと引き受けてる?🔌
- 依存の向きが Adapters → App → Domain になってる?➡️🧅
- 「迷ったら判定3問」で置き場所が決められる?🧠✨
2026年の周辺事情ミニメモ🧷✨(最新リサーチ反映)
- TypeScript は npm 上で 5.9.3 が “Latest” 表示だよ(2025/09/30公開として掲載) (npmjs.com)
- Node.js は v24 が Active LTS、v25 が Current として一覧に載ってるよ(2026/01/12更新) (Node.js)
- ESLint は v10.0.0-rc.0 のリリースノートが 2026/01/09 に出てるよ(v10移行の話題が熱い🔥) (ESLint)
- typescript-eslint は GitHub Releases に v8.53.0(2026/01/12) が “Latest” 表示だよ (GitHub)
次の章(第6章)では、この層モデルをさらに強くするために TypeScriptの type/interface を“契約”として使うやり方をガッツリやって、中心がもっと守れるようにするよ〜📜🧡✨