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

第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モデルはこうなる👇

  • CreateTaskRequesttitle だけ
  • CompleteTaskRequesttaskId だけ
  • ListTasksRequest:フィルタがあればそれだけ

Requestモデルのフィルタリングイメージ


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 だと、userIdtaskId 間違えても気づきにくい😭 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がめちゃくちゃ美しくなるよ〜🥰✨