第19章:Request Model(入力データ)を整える📨✨
今日のゴール🎯
- 「APIで受け取った形(DTO)」と「UseCaseがほしい形(Request Model)」を分ける理由を説明できる😊
- Request Modelを**“必要最小限”**にして、設計がブレないコツを掴む🧠🔧
- Controller / EndpointでDTO → Request Model 変換をスッと書けるようになる✍️✨
1) Request Modelってなに?🤔📦

Request Model(入力モデル)は、**UseCaseに渡すための“入力専用の箱”**だよ📨✨ ポイントは「内側(UseCase)が扱いやすい形にして渡す」こと!
Uncle Bobも「境界をまたいでデータを渡すなら、内側にとって都合のいい形で渡すべき」って趣旨をはっきり書いてるよ🧼🧱 (クリーンコーダーブログ)
2) なんでAPI DTOと分けるの?🍱➡️🧠
API DTOは「外側(HTTP/JSON)」の都合が強いよね📡
- JSONの項目名
- 互換性のための古いフィールド
- フロント都合の形(ネスト、配列、表示用っぽい項目)
DTOはネットワーク越しに運ぶための形(Data Transfer Object)としてよく使われるし、プレゼン層とドメインを切り離す目的があるよ📦✨ (Microsoft Learn)
でもそのDTOをそのままUseCaseに入れちゃうと…😵
- フロントの変更=UseCase変更、になりやすい
- JSON都合(null許容とか)に引きずられて、UseCaseがグラつく
- 「本当に必要な入力」が見えなくなる
だから、**DTOは外側で受けて、UseCaseにはRequest Modelとして“整えて渡す”**のが王道だよ😊🔌
3) “いいRequest Model” 7つのルール🌟✅
ルール1:UseCaseが本当に必要なものだけ🎯
「DBに入れる全項目」じゃなくてOK🙆♀️ そのユースケースに必要な入力だけにする!
ルール2:HTTP/JSONの匂いを持ち込まない🚫🌐
Request Modelに入れない例👇
HttpContext/ClaimsPrincipalIFormFile(ファイルは外で受けて、必要情報だけ渡す)- JSON属性(
JsonPropertyNameとか)
ルール3:名前は“ユースケースの言葉”にする🗣️✨
DTOの title より、UseCase側は MemoTitle / Title みたいに
意味が伝わる命名が強い💪
ルール4:null前提をやめる(できる範囲で)🧼
外側はnullが来るかもだけど、内側は「来ない前提」に寄せたい😊
- 必須は必須
- 任意は
string?などで明示
ルール5:不変条件はDomainで守る🚧💎
形式チェック(文字数とか空文字とか)は入口でもやるけど、 最終的に「壊れた状態を作れない」はDomain側が強い💪✨
ルール6:1ユースケース = 1 Request Model が基本📦
Create用とUpdate用は分けた方がスッキリしやすい😊 (“全部入り万能Request” は肥大化しがち😇)
ルール7:変換はAdapter(Controller/Endpoint)で一箇所に集める🔁
DTO→Request変換が散らばると地獄👹 入口でまとめて変換しよう!
4) 例:メモ作成(CreateMemo)で作ってみよう📝✨
4-1. Entities側(すでにある想定のVO)💎
(第9〜10章で作ったイメージね!)
namespace MyApp.Core.Entities;
public readonly record struct UserId(Guid Value);
public sealed record MemoTitle
{
public string Value { get; }
public MemoTitle(string value)
{
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("タイトルは必須です");
if (value.Length > 100) throw new ArgumentException("タイトルは100文字までです");
Value = value;
}
}
public sealed record TagName
{
public string Value { get; }
public TagName(string value)
{
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("タグ名は必須です");
if (value.Length > 30) throw new ArgumentException("タグ名は30文字までです");
Value = value;
}
}
ここで大事なのは「Request ModelがVOを使える」ってこと💡 UseCasesはEntitiesを参照できる(内側へ)ので、意味が強くなるよ😊🧠
4-2. UseCases側:Request Model(最小の入力)📨
using MyApp.Core.Entities;
namespace MyApp.Core.UseCases.Memos.Create;
public sealed record CreateMemoRequest(
UserId OwnerId,
MemoTitle Title,
IReadOnlyList<TagName> Tags
);
OwnerId:誰のメモ?(認可の“主体”情報は IDだけ渡すのがコツ)👤Title:VOで意味を固定💎Tags:必要なら。不要なら削ってOK✂️
4-3. Input Port(18章の続きとつながる)🔌⬅️
namespace MyApp.Core.UseCases.Memos.Create;
public interface ICreateMemoInputPort
{
Task HandleAsync(CreateMemoRequest request, CancellationToken ct);
}
ここで UseCaseの入口(窓口) が固まるよ😊✨
5) Web側:API DTO を受けて Request Model に変換する🍱➡️📨
5-1. API DTO(外側の都合)🌐
namespace MyApp.Web.ApiContracts;
public sealed class CreateMemoDto
{
public string? Title { get; set; }
public List<string>? Tags { get; set; }
}
5-2. Endpointで変換(ここが“境界”🚪)
using MyApp.Core.Entities;
using MyApp.Core.UseCases.Memos.Create;
using MyApp.Web.ApiContracts;
app.MapPost("/memos", async (
CreateMemoDto dto,
ICreateMemoInputPort useCase,
HttpContext http,
CancellationToken ct) =>
{
// 例:ログインユーザーIDを外側で取得して、IDだけ渡す👤
var ownerId = new UserId(Guid.Parse(http.User.FindFirst("sub")!.Value));
// DTO -> Request Model(UseCaseが欲しい形へ整える✨)
var request = new CreateMemoRequest(
OwnerId: ownerId,
Title: new MemoTitle(dto.Title ?? ""),
Tags: (dto.Tags ?? new List<string>())
.Select(t => new TagName(t))
.ToList()
);
await useCase.HandleAsync(request, ct);
return Results.Ok();
});
ここが超重要ポイント💖
- DTOは外側の形🍱
- Request ModelはUseCaseの形📨
- 変換で“波”を止める🌊✋
6) バリデーション、どこでやるの?🛑🧠
よく混乱するから、シンプルに分けよう😊
-
Adapter(入口):形式っぽいチェック
- nullチェック
- JSONの形式
- “空っぽで来てる” みたいな雑な入力
-
Domain(VO/Entity):ルール(不変条件)
- 「空タイトル禁止」
- 「100文字まで」
- 「タグは30文字まで」
こうすると、どこに何を書くか迷子になりにくいよ🧭✨
7) Request Modelが肥大化してきたサイン🚨
サインA:DTOのフィールドがそのまま増殖してる🧟♀️
→ 「本当にUseCaseで必要?」を毎回問い詰めよう😆🔍
サインB:表示用っぽい情報が混ざってきた🎀
例:DisplayTitle とか LocalizedDateText とか
→ それ、Presenter側の仕事だよ〜!🎤✨
サインC:1つのRequestが“万能”になってる🧰
→ Create/Update/Searchで分割しよ😊✂️
8) ミニ課題(手を動かそう)🧪✨
-
CreateMemoRequestからTagsを一度消す✂️
- 本当に必要か、要件から判断してみてね😊
-
UpdateMemoRequestを作る🛠️
MemoId(VOでもOK)と、更新可能な項目だけ
-
EndpointでDTO→Request変換を実装して、変換場所が散らばってないか確認👀✅
9) Copilot / Codexに頼むと便利な聞き方🤖💬
- 「このユースケース説明から、Request Modelの必須フィールドだけ抽出して」
- 「DTOにあるけどUseCaseに不要そうな項目を指摘して」
- 「DTO→Request変換コードを書いて。null/空の扱いも提案して」
- 「Request Modelが肥大化する兆候チェックリストを作って」
まとめ🎉
- Request Modelは UseCaseの入力専用の箱📨
- DTO(外側の形)をそのまま入れず、境界で整えて渡す🚪✨(内側に都合のいい形で渡すのが筋)(クリーンコーダーブログ)
- DTOは層を分けるのに役立つけど、だからこそ UseCase側とは分離が効く🍱➡️🧠 (Microsoft Learn)
次の第20章では、このRequest Modelを受け取って動く Interactorの骨格を組み立てていくよ🧱🔥