第15章:Input Boundary:Requestモデル設計📥
(Past chat)(Past chat)(Past chat)(Past chat)(Past chat)
この章はね、「外から来た入力(ユーザー操作・JSON・フォーム)」を UseCaseが食べやすい形に整えるための“型(モデル)”を決める回だよ〜😊💕
ポイントはこれ👇
- UseCaseの入力は HTTPとかUIの都合を一切持ち込まない 🚫🌐
- でも、外から来る入力はだいたい 汚い(欠けてる/型が違う/余計なものが混ざる) 😵💫
- だから Requestモデル=“関所” みたいにして、UseCaseを守る🛡️✨
TypeScriptの最新(本日時点)は TypeScript 5.9 が公式リリースノートとして提供されてるよ📌 (typescriptlang.org)
1) 今日のゴール🎯💖
この章が終わったら、こんな状態になってるのが理想だよ✨
- ✅ Create/Complete/List の Requestモデルを型で定義できる
- ✅ Requestに HTTPっぽい言葉(req/res, statusCode, headers…)を混ぜないで済む
- ✅ 「外から来たデータ」を Requestへ詰め替える時のルールが決まる
- ✅ テストしやすい(Requestがただのデータになる)🧪✨
2) Requestモデルってなに?📦🧸
Requestモデルは、UseCaseに渡す入力を “UseCase都合の形”にした箱だよ🎁✨
例:CreateTaskなら
- 外側(UI/HTTP)では
"title"が空かも / 数字かも / 変な空白が混ざるかも 😇 - でもUseCaseは「タスクを作る」ために 最低限これだけ欲しい →
title: string
だから、Requestモデルはこうなる👇
- CreateTaskRequest:
titleだけ - CompleteTaskRequest:
taskIdだけ - ListTasksRequest:フィルタがあればそれだけ

3) 絶対ルール:Requestに入れちゃダメなもの🚫🧨
Requestは UseCase層のものだから、外側の匂いを入れると崩れやすいよ🥲
入れないでね👇
- ❌
Request,Response,headers,cookies,statusCode - ❌
express.Requestみたいなフレームワーク型 - ❌ DBっぽいもの(
row,record,limit/offset(SQL都合)) - ❌ UI都合の名前(
titleInputValueとかformStateとか)
「UseCaseが欲しい入力の最小セット」だけを、静かに置くのが勝ち✨🏆
4) Requestモデル設計のコツ5つ🧠✨
コツ1:“UseCaseの質問”にだけ答える形にする💬
CreateTaskは「タイトル何?」だけ聞いてるのに、createdAt とか入れない😌
コツ2:内側の言葉で命名する📖
taskId, title みたいに業務語彙で統一✨
コツ3:idはなるべく“ブランド型”にする🆔✨
ただの string だと、userId と taskId 間違えても気づきにくい😭
TypeScriptなら“ブランド型”で事故を減らせるよ🚑✨
コツ4:Requestは基本 immutable(readonly)に寄せる🧊
「あとから書き換えOK」にすると、原因不明バグが増える😵💫
コツ5:“型(コンパイル時)”と“実行時チェック”を混同しない⚠️
TypeScriptの型は実行時に消えるよね。 外から来る入力(JSON等)は、必要なら 実行時バリデーションも使う(後でController側で)🧪✨ Zodみたいなライブラリは「parseしたら型安全」って発想ができるよ📌 (Zod)
5) 実装してみよう💻✨(Requestモデル3つ)
5-1. 型の土台:Brand型を用意🧷✨
src/usecases/_shared/brand.ts
export type Brand<T, B extends string> = T & { readonly __brand: B };
TaskId を作る👇
src/entities/task-id.ts(Entitiesに置いてもOK。ここでは分かりやすさ優先で例として)
import type { Brand } from "../usecases/_shared/brand";
export type TaskId = Brand<string, "TaskId">;
export const TaskId = {
// ここでは「ブランド付け」だけ。実行時チェックは別でやる想定。
of(value: string): TaskId {
return value as TaskId;
},
};
ブランド型は「型の事故防止」のためのテクだよ✨ ちなみに
satisfies演算子(TypeScript 4.9〜)も “型を守りつつ推論を壊さない” のに便利!📌 (typescriptlang.org)
5-2. CreateTaskRequest📥🗒️
src/usecases/create-task/create-task-request.ts
export type CreateTaskRequest = Readonly<{
title: string;
}>;
設計ポイント💡
idは入れない(IdGenerator Portが作る予定だから)titleだけで十分✨
5-3. CompleteTaskRequest✅🆔
src/usecases/complete-task/complete-task-request.ts
import type { TaskId } from "../../entities/task-id";
export type CompleteTaskRequest = Readonly<{
taskId: TaskId;
}>;
設計ポイント💡
taskIdは ただのstringじゃなく TaskId にして事故を防ぐ🛡️✨
5-4. ListTasksRequest👀📋
src/usecases/list-tasks/list-tasks-request.ts
export type ListTasksRequest = Readonly<{
onlyIncomplete?: boolean; // 例:未完了だけ欲しい
}>;
設計ポイント💡
- 「一覧」が必要とする条件だけ
- SQLの
limit/offsetはここでは入れない(DB都合だから)🙅♀️
6) Requestを作る側(外側)での“詰め替え”ルール🧃✨
ここ超大事〜! Requestモデル自体はUseCase側に置くけど、Requestを作るのは外側(Controller/Inbound Adapter) だよ🚪✨ (本格的な変換は第31章でやるけど、この章でも“ルール”だけ固めちゃおう)
ルール✅
- 外側の生データは
unknownとして受ける(信用しない😇) - 変換した結果が Request
- Requestを作るとき、
satisfiesで形チェックすると安全✨ (typescriptlang.org)
例(Controller側のイメージ):
import type { CreateTaskRequest } from "../usecases/create-task/create-task-request";
const req = {
title: String(input.title ?? "").trim(),
} satisfies CreateTaskRequest;
ここで「余計なプロパティ」や「足りないプロパティ」を早めに気づけるのが嬉しいやつ🥰
7) よくある失敗あるある😵💫💥
- ❌ Requestに
statusCodeとか入れ始める(HTTPの侵食) - ❌ Requestに
TaskRecord(DB行)をそのまま入れる(DBの侵食) - ❌ UIのフォーム状態をそのまま入れる(UIの侵食)
- ❌ 「便利そう」で巨大Requestにする(UseCaseがブヨブヨに)🐷
合言葉はこれ👉 Requestは“UseCaseが欲しい最小セット”だけ🌸
8) 練習問題✍️💕
Q1 ✅
CreateTaskRequest に id を入れたくなりました。なぜやめた方がいい?(一言でOK)
Q2 ✅
ListTasksRequest に limit/offset を入れたくなりました。どこに置くのが筋?(層で答えてね)
Q3 ✅(発展)
新ユースケース「RenameTask」を追加するなら、Requestはどんな形にする? (ヒント:UseCaseが必要な最小情報だけ✨)
9) AI相棒プロンプト(コピペ用)🤖✨
- 💡Request設計案を出させる
Create/Complete/List のUseCaseに対して、UseCaseが必要な最小入力だけで Request 型を設計して。
HTTP/DB/UI都合の項目は入れないで。各Requestの理由も1行で。
- 💡命名チェック
このRequest名/プロパティ名は「内側の言葉」になってる?外側(HTTP/DB/UI)の匂いがあれば改善案を出して。
- 💡Brand型の導入チェック
TaskIdをstringのまま使うリスクを列挙して、Brand型にした場合の改善点と注意点を教えて。
まとめ🎀✨
- Requestモデルは UseCaseに渡す“きれいな入力箱” 📦
- 外側の言葉(HTTP/DB/UI)を入れないのが最重要🚫
- TypeScriptでは Brand型や satisfies で事故を減らせる🛡️✨ (typescriptlang.org)
- 実行時チェックが必要なら、Zodみたいな仕組みを“外側”で使うと相性いいよ🧪✨ (Zod)
次の第16章は Output Boundary:Response設計📤 だね! RequestとResponseが揃うと、UseCaseがめちゃくちゃ美しくなるよ〜🥰✨