11章:ACLの核① マッピングの基本(DTO→ドメイン)🔁📦
この章でできるようになること 🎯✨
- 外部APIの DTO(外の都合) を、アプリ内部の ドメイン型(自分の都合) に変換できるようになる🙌
- 「変換関数(mapper)」を どこに置く?どう命名する? が迷わなくなる🧭
- DTOが内側に漏れない、キレイな境界が作れるようになる🧼🧱
11-1. まず超重要:DTOとドメイン型は“別物”だよ 🧾🆚📘
DTO(Data Transfer Object)=外の都合 🧳
- 外部APIのレスポンスそのまま
- 命名が変・コード値が謎・null多め・単位が不思議…みたいな “クセ” が混ざる😇
- 変更されやすい(相手が仕様変えたら即しんどい)💥
ドメイン型=内側の都合(あなたのルール)🏠✨
- アプリの中で読みやすく、意味が明確
- 不変条件(ルール)を守れる形にしたい🔒
- “あなたのアプリの言葉” だけで完結させたい📚
✅ 結論:DTOは内側に入れない。入れる前に“翻訳”する。それがACLの核🔥
11-2. マッピング(変換)の基本形はこれだけ ✨
マッピングは基本、こういう流れだよ👇

- DTOを受け取る 📥
- 必要なら整形(trim/数値化/日付化…)🧽
- ドメイン型を作って返す 📤
ポイントはこれ👇
- I/Oしない(HTTP叩かない、DB触らない)🙅♀️
- 純粋関数にする(同じ入力→同じ出力)🧪✨
- 例外や失敗をどう返すかは“設計”だけど、最初はシンプルでOK👌
11-3. 変換関数はどこに置く?命名どうする?🗂️✍️
おすすめの置き場所(例)📁
-
src/acl/...の下にまとめるsrc/acl/dto/… 外部DTOの型src/acl/mappers/… DTO→ドメイン変換src/acl/adapters/… Port実装(外部呼び出し+変換)
“ACL配下に変換が集まる”と、境界が見えやすいよ👀✨
命名ルール(迷ったらこれ)📝
-
DTO型:
StudentDto/PaymentResponseDtoみたいに Dtoを末尾 -
変換関数:
toStudent()(短くて使いやすい)mapStudentDtoToStudent()(明示的で安全)
-
ファイル名:
student.mapper.tsみたいに役割がわかる名前📌
11-4. ハンズオン:DTO → ドメインに変換してみよう 🧑💻✨
ここから手を動かすよ〜!🧤🔥 題材は「学生情報API(外部)」→「アプリ内のStudent(内側)」に変換する例📦➡️📘
(2026年1月時点でTypeScriptは5.9系が最新安定版として配布されています。)([npmjs.com][1]) (Node.jsは v24 が Active LTS で提供されています。)([Node.js][2])
11-4-1. 外部DTO(例)を定義しよう 🧾
// src/acl/dto/student-directory.dto.ts
// 外部APIが返してくる “外の都合” の形
export type StudentDirectoryStudentDto = {
stu_id: string; // "A00123" みたいな文字列ID
name_kana?: string | null; // nullが来ることも…
name: string;
grade_cd: "1" | "2" | "3" | "4" | "9"; // "9" が何か不明…😇
point: string; // "1200" みたいに数値が文字列…
updated_at: string; // ISOっぽい文字列
};
DTOは「うん、こう来るんだね…」でOK。直さない。直すのは変換側💪✨
11-4-2. ドメイン型(内側の言葉)を作ろう 📘✨
ここでは超シンプルにいくよ😊 (ValueObjectをガチるのは別章でOK👌)
// src/domain/student.ts
export type StudentId = string & { readonly __brand: "StudentId" };
export function StudentId(value: string): StudentId {
// ここで最低限のチェックをしたくなるけど…
// 今章では「作れる形に整える」までにして、厳密な検証は後の章でやろう✨
return value as StudentId;
}
export type StudentType = "UNDERGRAD" | "GRAD" | "UNKNOWN";
export type Student = {
id: StudentId;
name: string;
nameKana: string | null;
studentType: StudentType;
points: number;
updatedAt: Date;
};
11-4-3. いよいよ変換(mapper)を書くよ 🔁🔥
```ts
// src/acl/mappers/student.mapper.ts
import { Student, StudentId, StudentType } from "../../domain/student";
import { StudentDirectoryStudentDto } from "../dto/student-directory.dto";
function toStudentType(gradeCd: StudentDirectoryStudentDto["grade_cd"]): StudentType {
// 例:学部生=1-4、大学院=9 と仮定(外部の仕様に依存する場所はここに閉じ込める🧱)
if (gradeCd === "9") return "GRAD";
if (gradeCd === "1" || gradeCd === "2" || gradeCd === "3" || gradeCd === "4") return "UNDERGRAD";
return "UNKNOWN";
}
function toPoints(point: string): number {
// "1200" → 1200
const n = Number(point);
// NaN対策(ここで落とす/0にする/UNKNOWNにする…は設計)
return Number.isFinite(n) ? n : 0;
}
export function toStudent(dto: StudentDirectoryStudentDto): Student {
return {
id: StudentId(dto.stu_id),
name: dto.name,
nameKana: dto.name_kana ?? null,
studentType: toStudentType(dto.grade_cd),
points: toPoints(dto.point),
updatedAt: new Date(dto.updated_at),
};
}
### ここが最高に大事ポイント 💎
* 外部の謎(`grade_cd`、`point` が文字列…)は **このファイルに隔離**🧱
* ドメイン側は **読みやすい&意味がある型** だけになる📘✨
* 外部が変わっても、修正ポイントは **mapper周辺に集中**🔧
---
# 11-5. “変換の責任”を太らせすぎないコツ 🍔➡️🥗
mapperは便利だから、油断するとこうなる👇😇
* 変換しながらHTTP叩く
* 変換しながらDB問い合わせ
* 変換しながら巨大なif地獄
✅ まず守るルールはこれだけでOK💡
* mapperは **データの形を変えるだけ**
* 迷ったら「小さい関数に分ける」✂️✨(`toStudentType`みたいに)
---
# 11-6. ランタイム検証(ちょい最新トレンド)🧪✨(※軽く触れるだけ)
TypeScriptは「型」は強いけど、外部から来たデータが本当に型どおりかは保証できないよね😵💫
そこで最近は **Zodみたいなスキーマ検証**をACLで使う人が多いよ🧼✨
* Zod v4 は安定版として公開されていて、npm上でも継続的に更新されています。([Zod][3])
ただし!今章は **“マッピングの基本”** が主役なので、
本格的な「パース」「バリデーション」は **14章・15章でガッツリ**やろうね🧽✅
---
# 11-7. AI拡張(Copilot / Codex)を使うと爆速になる場面 🤖💨
GitHub Copilot は VS Code の中で、コード提案・説明・実装の補助をしてくれるよ。([Visual Studio Code][4])
## 使いどころ①:DTO→ドメイン変換の“下書き”を出してもらう ✍️
Copilot/Codexに投げる指示例👇(そのまま貼ってOK)
* 「このDTOをStudentに変換する `toStudent(dto)` を作って。`grade_cd` は enum にして、未知値は `UNKNOWN` にして」
* 「`point: string` を number にして、NaNなら0にして」
* 「mapperを小さい関数に分けて読みやすくして」
## 使いどころ②:境界ケースを洗い出す 🧠💡
* 「null/undefined/空文字/NaN/未知コードのケースを列挙して」
→ 次章以降のテストや検証にそのまま使えるよ🧪✨
⚠️ ただし:外部仕様の“意味”(例:`grade_cd="9"`が何か)は、**人間が決める**のが安全だよ🛡️🙂
---
# 11-8. ミニ演習(手を動かすと定着するよ)🎮✨
## 演習A:未知コードをログ用に残せる形にしてみよう 📝
* `toStudentType()` で `UNKNOWN` になったとき、あとで観測できるように
`unknownGradeCd?: string` を `Student` じゃなくて「ACL側の結果」に持たせる案を考えてみてね💡
(※ログや観測は20章でやるけど、設計の発想として◎)
## 演習B:`updated_at` が変な文字列のときどうする?⏰😇
* `new Date(dto.updated_at)` が `Invalid Date` になったら?
* 落とす?
* 代替値?
* “扱えないデータ”として隔離?🚦
どれがあなたのアプリに合うか、方針を1つ決めてみよう✨
---
# 11-9. この章のチェックリスト ✅🧼
* [ ] DTO型(`*Dto`)が `src/domain/` に入り込んでない?🚫
* [ ] mapperがHTTP/DBに触ってない?🙅♀️
* [ ] “外の謎”は mapper/ACL配下に閉じ込められてる?🧱
* [ ] ドメイン型は読みやすい言葉になってる?📘✨
* [ ] 変換がデカくなりそうなら、小さい関数に分けた?✂️
---
次の章(12章)では、命名変換・構造変換をもっと気持ちよくして「内側の言葉の品質」を爆上げしていくよ〜!🧾➡️📘✨
[1]: https://www.npmjs.com/package/typescript?utm_source=chatgpt.com "TypeScript"
[2]: https://nodejs.org/en/about/previous-releases?utm_source=chatgpt.com "Node.js Releases"
[3]: https://zod.dev/v4?utm_source=chatgpt.com "Release notes"
[4]: https://code.visualstudio.com/docs/copilot/overview?utm_source=chatgpt.com "GitHub Copilot in VS Code"