第10章:DTOと境界変換📦(画面の形をDomainに入れない)

この章、めっちゃ大事です…!🥹💖 レイヤードで「キレイに分けたはずなのに、いつの間にかドメインが画面都合まみれ」って事故を防ぐ回だよ〜🚧💥
0. この章でゴールにしたいこと🎯✨
読み終わったら、こんな状態になってるのが理想!😊
- DTOって何で必要なのか、説明できる📦
- 「Domainモデル」と「DTO」を混ぜない習慣がつく🧼
- 変換(Mapping)をどこに置くか迷わない🧭
- DTOが増えても散らからない整理術がわかる🧹✨
- AI(Copilot/Codex)で変換コードをラクに作って、でも設計は崩さない🤖🧠
1. DTOってなに?📦(超ざっくりでOK!)
DTO(Data Transfer Object)は、いってしまえば
「境界をまたぐときの、運搬用の箱」📦🚚
です!
- UI ⇄ API のやりとり
- Presentation ⇄ Application のやりとり
- 外部サービスとの連携 などなど
「境界」をまたぐときに、必要な形だけを運ぶために使うよ😊✨
Microsoftのドキュメントでも、DBエンティティをそのまま外に出すのは良くないことがあるよ〜って話が出てくるよ。(Microsoft Learn)
2. なぜDTOが必要?(Domainを守るため💎🛡️)
DTOがないと、だいたいこうなる👇💥
- 画面都合の項目(表示用の文字列、チェックボックス、画面専用フラグ)が Domainに侵入😇➡️😱
- APIの仕様変更が Domainモデルの変更になってしまう🌀
- そのうち「このプロパティ何のため?」が増える🥲
DTOを使うと嬉しいことは、特にこの3つが強い💪✨
① セキュリティ(Overposting対策)🔐
「送ってほしくない項目」まで受け取っちゃう事故を減らせるよ! ASP.NET Coreでも、Overposting対策として ViewModel(=DTO的なもの)推奨って書かれてる👍(Microsoft Learn)
② API/UIの“形”とDomainの“意味”を分離できる🧠✂️
- DTOは 都合の形(表示・通信・入力フォーム)
- Domainは 意味とルール(正しい状態・不変条件)
これが分かれると、強い✨
③ 変更に強くなる🧱
DTOの変更=契約(外との約束)の変更 Domainの変更=業務ルールの変更 …って分かれると、影響範囲が小さくなるよ😊
3. DTOの種類を整理しよ📦📦📦(これで迷子にならない)
DTOって一口に言っても、用途があるよ〜✨
- Request DTO:受け取る用(例:CreateTodoRequest)
- Response DTO:返す用(例:TodoResponse)
- ViewModel:画面専用(MVCやフロント向けに整形)
- Command/Query:Application層の入出力(ユースケースの言葉)
- Integration DTO:外部API連携用(相手の仕様に合わせる)
- Persistence Model:DB都合(※これはInfrastructureで扱うことが多い)
ポイントはこれ👇💡 **「DTOは“目的別に分ける”ほど安全」**🛡️✨ 「万能DTO」を作ると、だいたい育って神クラス化するよ😇💥
4. 変換(Mapping)をどこに置く?🧩(ここが第10章の芯!)
結論:“境界の近く”に置く🚪✨
境界の近く=「その形を知っていい場所」だよ!
✅ 基本ルール(迷ったらこれ!)
- Presentation:Request/Response DTO ⇄ Application入力(Command/Query)
- Application:UseCase入力 ⇄ Domainモデル(必要なら)
- Domain:DTOを知らない🙅♀️(ここが超重要💎)
Domainは「画面の形」「APIの形」「DBの形」を知らないのが美しい✨
5. ハンズオン:ToDoでDTOと変換を体験しよ🐟📝✨
ここでは分かりやすく Minimal APIっぽい形で例を出すね! (Minimal API自体は公式チュートリアルもあるよ🧑🏫)(Microsoft Learn)
🎬 シナリオ:ToDoを追加する
- クライアント → API:
CreateTodoRequest - Application:
CreateTodoCommand - Domain:
TodoItemを生成(ルールで守る🔒) - API → クライアント:
TodoResponse
5-1. Presentation側DTO(受け取り用・返す用)📦✨
// Presentation層(例: *.Presentation)
// 受け取り用(入力フォームの形)
public sealed record CreateTodoRequest(
string Title,
string? Note
);
// 返す用(表示したい形)
public sealed record TodoResponse(
Guid Id,
string Title,
string? Note,
bool IsDone
);
recordはDTOに相性いいよ〜😊(“運搬用”だから)📦✨ ちなみにC#の最新は C# 14(.NET 10対応)だよ。(Microsoft Learn)
5-2. Application側(ユースケースの言葉)🎮📋
// Application層(例: *.Application)
public sealed record CreateTodoCommand(
string Title,
string? Note
);
public interface ITodoService
{
Task<TodoResult> CreateAsync(CreateTodoCommand command, CancellationToken ct);
}
public sealed record TodoResult(
Guid Id,
string Title,
string? Note,
bool IsDone
);
ここで大事なのは👇 **Applicationの入出力は “ユースケースの言葉”**📋✨ APIの都合(JSONの形)とは切り離すよ〜😊
5-3. Domain(意味とルールの世界)💎🔒
// Domain層(例: *.Domain)
public sealed class TodoItem
{
public Guid Id { get; }
public string Title { get; private set; }
public string? Note { get; private set; }
public bool IsDone { get; private set; }
private TodoItem(Guid id, string title, string? note)
{
if (string.IsNullOrWhiteSpace(title))
throw new ArgumentException("Titleは必須だよ!", nameof(title));
Id = id;
Title = title.Trim();
Note = note;
IsDone = false;
}
public static TodoItem CreateNew(string title, string? note)
=> new(Guid.NewGuid(), title, note);
public void MarkDone() => IsDone = true;
}
✅ DomainはDTOを一切知らない! これが “守りの強さ” になるよ🛡️💎
6. 変換はどう書く?(まずは手書きが最強✍️✨)
初心者のうちは、まず 手書きマッピング推奨!✍️ 理由は「何がどこへ流れてるか」が体に入るから😊
6-1. Presentation → Application(境界変換)🚪📦
// Presentation層に置くのが分かりやすい(DTOの形を知っていい場所)
public static class TodoMappings
{
public static CreateTodoCommand ToCommand(this CreateTodoRequest req)
=> new(req.Title, req.Note);
public static TodoResponse ToResponse(this TodoResult result)
=> new(result.Id, result.Title, result.Note, result.IsDone);
}
6-2. Application → Domain(必要なら)🧩💎
「ApplicationがDomain生成の入口になる」イメージ✨
// Application層(Domainを組み立てるのはOK)
public static class DomainMappings
{
public static TodoItem ToDomain(this CreateTodoCommand cmd)
=> TodoItem.CreateNew(cmd.Title, cmd.Note);
}
7. DTOが増えた時の整理術🧹✨(散らからない仕組み)
DTOが増えるときは、だいたいこの2パターンで増えるよ📈
- 画面やAPIが増える(表示項目が違う)📱🖥️
- 外部連携が増える(相手仕様が違う)🌍🔌
✅ おすすめ整理ルール(すぐ効く!)
- 機能(Feature)単位でフォルダを切る📁✨
例:
Todos/Create/Todos/List/Todos/Detail/ - Request/Responseを分ける(同じDTOを使い回さない)🧠✂️
- Mappingも同じFeature内に置く(迷子防止)🧭
8. 自動マッピングどうする?🤖✨(使いどころが大事!)
8-1. AutoMapperについて(2026時点の注意点⚠️)
AutoMapperは便利なんだけど、v15からライセンスが必要になってるよ。(docs.automapper.io) NuGetの最新版としては 16.0.0 などが出てる(時期によって変わる)よ。(NuGet)
学習用・小規模なら手書きで十分✨ 仕事で使うなら、チームの方針とライセンスを要確認だよ〜🧐
8-2. いま人気の流れ:Source Generator系(例:Mapperly)⚡
Mapperlyは ビルド時にマッピングコードを生成してくれるタイプ! 公式でも「source generator」「高速」「生成コードが読める」って説明されてるよ。(Mapperly)
(おまけ)「ランタイム魔法」より「生成されたコードが読める」方が、設計学習には相性よいこと多いよ😊✨
9. よくある事故パターン集🚑💥(先に潰す!)
❌ 事故①:DomainにDTOを置く
- Domainが「画面の形」を知ってしまう → 将来、画面都合でDomainが壊れる😇💥
❌ 事故②:DTOを“共通化”して全画面で使う
- 「一覧に要る項目」と「詳細に要る項目」が違うのに同じ箱で運ぶ → DTOが肥大化してカオス化📦🐘
❌ 事故③:Controller/Endpointで全部やる(薄くない)
- Validation
- Mapping
- UseCase
- 例外処理
- ログ …が全部入り、すぐ太る🐷💥
👉 変換はOKだけど、**“変換だけ”**にしようね✨
10. 章末ミニ演習🎓📝✨(手を動かすと定着する!)
演習A:DTOを分けてみよう📦
CreateTodoRequestとTodoResponseを作る- Domainには入れない(入れたくなったら負け😇)
演習B:表示都合の項目を足してもDomainを汚さない🌸
例:Responseに StatusText("未完了"/"完了")を追加
→ これ、Domainに持ち込まないでResponse側で作れる?😊
演習C:マッピングのユニットテスト🧪
「Titleが空ならDomainで弾かれる」をテストしてみよう✨
11. 章末チェックリスト✅✨(これ通ったら勝ち!)
- Domain層にDTOが1個もいない🙅♀️💎
- Request/Response DTOを分けてる📦📦
- 変換コードが“境界の近く”にある🚪✨
- DTOが肥大化してきたらFeature単位に整理できる📁🧹
- 「画面都合の項目」をDomainに入れずに済む😊🌸
12. よくある質問Q&A🙋♀️💬
Q1. DTOって絶対必要?小さいアプリでも?
小さいうちは「最小限でOK」だよ😊 でも、境界(外と約束)があるならDTOは強い! 特にAPIは後から仕様が変わりやすいから、DTOあると守りが固い🛡️✨
Q2. “ApplicationのCommand”もDTOみたいなもの?
そうそう!😊 ただし役割が違うよ👇
- Request DTO:外との約束(入力の形)
- Command/Query:ユースケースの言葉(中の手順の入口)
Q3. 変換コード、どこに置くか毎回迷う…
迷ったらこれ!👇 **「その“形”を知っていい場所に置く」**🚪✨ APIの形はPresentation、ユースケースの形はApplication、Domainは知らない🙅♀️💎
13. AI活用プロンプト例🤖✨(便利だけど主導権はこっち!)
✅ DTO設計を相談する
ToDo追加APIを作っています。
Domain(TodoItem)を汚さずに、Request/Response DTOを分けたいです。
DTOの設計案と、各層(Presentation/Application/Domain)に置くべき型を提案して。
ついでに「やりがちな事故」も指摘して。
✅ マッピングコードを生成してもらう(でもレビュー必須!)
CreateTodoRequest → CreateTodoCommand → TodoItem の変換を手書きで作りたい。
拡張メソッド or Mapperクラスの形で、読みやすい実装を出して。
置き場所は「境界の近く」を守って。
✅ レイヤー違反チェックをしてもらう
この差分で、Domain層がPresentationやInfrastructureの型に依存していないかチェックして。
依存が混ざっていたら、どこに移すべきかも提案して。
14. (最新事情メモ🗞️)2026時点で押さえておくと良い話✨
- .NET 10 は 2025/11/11 にリリースされたLTSで、サポート表も公開されてるよ📅(Microsoft)
- C# 14 が最新で、.NET 10でサポートされてるよ✨(Microsoft Learn)
- AutoMapperは v15 からライセンスが必要(導入時に注意⚠️)(docs.automapper.io)
- MapperlyみたいなSource Generator系マッパーも選択肢として広く使われてるよ⚡(Mapperly)
次の章(第11章)は「Infrastructure層の考え方🚪(外側に押し出す)」だから、 この第10章で作った“境界の感覚”がめっちゃ効いてくるよ〜😊✨