第30章:小まとめ:DDDに効くTS要素を復習🧡🔁
この章はね、「次の第31章(Value Object💎)」に入る前に、ここまでの基礎体力(TSの武器)を“使える形”に固める回だよ〜!🥳✨ いったん整理すると、戦術DDDって「賢いクラスを作る」じゃなくて、**ルールを壊さないように“設計とコードで守る”**のが本体なのね🛡️
0) 2026/2/7時点の“最新TSまわり”だけ先にメモ📝✨
- TypeScriptの最新版は 5.9(公式のダウンロードページでも現行が 5.9 になってるよ)(TypeScript)
- いまの流れとしては、**TypeScript 6.0 が「橋渡し(bridge)」**で、そこから先は **TypeScript 7(ネイティブ移植)**へ、という方針が公式ブログで明言されてるよ(Microsoft for Developers)
- ネイティブ移植(超高速化)のプレビューも進んでいて、ツール体験が大きく変わる可能性がある、っていうのも公式に出てる🧠⚡(Microsoft Developer)
- テストは Vitest 4.0 が出て以降、モダンTSでかなり定番寄りになってる(公式アナウンスあり)🧪(vitest.dev)
- Node.jsは、2026年初め時点で v24がActive LTS になってる(公式のリリース一覧)(nodejs.org)
ただしこの章は「機能の新しさ」より、**DDDに効く“使い方の型”**が主役だよ〜☺️🧡
1) この章のゴール🎯✨
読み終わったら、これができればOK!
- ✅ **不変(immutable)**を“気分”じゃなく、コードの形で守れる🧊
- ✅ プリミティブ地獄(string/number乱舞)を卒業できる🧱
- ✅ **失敗(エラー)**を設計として扱える(例外/Resultの整理)🧯
- ✅ 境界(domain/app/infra + DTO)を崩さず進められる📦
- ✅ **テスト(AAA)**が“書けるし増やせる”状態になる🧪
そして最後に、理解チェック問題で自分の穴が見つかる🔍✨
2) まず「DDDに効くTS要素」5点セットを1枚にすると📌
戦術DDDの序盤は、だいたいこの5つで勝てるよ💪🧡
- 不変性🧊:勝手に状態が変わらない → バグが減る
- 型で意味を持たせる🏷️:数字じゃなく「金額」、文字列じゃなく「注文ID」
- 失敗の表現🧯:例外かResultか、混ぜない
- 境界と依存の向き🧱➡️:domainは内側。外(infra)を知らない
- テスト🧪:ルールを守り続ける保険(AAAで型を固定)
この5つが“筋肉”になると、第31章以降(VO/Entity/Aggregate)が一気にラクになるよ〜!🥹✨
3) 不変(immutable)🧊:DDDで最初に効く“バグ予防”
よくある事故💥
「配列を外から渡して、そのまま持ってたら、外でpushされて壊れた…😇」みたいなやつ!
コツはこれだけ🧊✨
- 外から受け取った配列はコピーする
- 外へ返す配列もコピーする
- 状態変更は“新しい値を作る”
例:配列を守る(コピーする)🛡️
class Order {
private readonly _items: ReadonlyArray<{ sku: string; qty: number }>;
private constructor(items: ReadonlyArray<{ sku: string; qty: number }>) {
// 外から来た配列をそのまま持たない!
this._items = [...items];
}
static create(items: ReadonlyArray<{ sku: string; qty: number }>) {
return new Order(items);
}
get items(): ReadonlyArray<{ sku: string; qty: number }> {
// 外へ返すときも守る(必要なら deep copy も検討)
return [...this._items];
}
addItem(sku: string, qty: number): Order {
// 変更じゃなく生成!
return new Order([...this._items, { sku, qty }]);
}
}
“不変”って、
readonlyって書くだけじゃなくて、参照の漏れを止めるのが本体だよ〜🧠🔒
🧸ミニ練習
items()がreturn this._items;になってたら、どう壊れる?説明してみてね🗣️✨
4) 型で守る🏷️:プリミティブ地獄🧱からの脱出
DDDって「意味のあるモデル」を作るのが強いのね。 TypeScriptだと、まずは **“専用型”**で効果が出るよ💡
4-1) branded type(ブランド型)で“IDの取り違え”を殺す🪪💥
type Brand<T, B extends string> = T & { readonly __brand: B };
type OrderId = Brand<string, "OrderId">;
type MenuItemId = Brand<string, "MenuItemId">;
const OrderId = (v: string) => v as OrderId;
const MenuItemId = (v: string) => v as MenuItemId;
// 取り違えがコンパイルで止まる✨
const oid = OrderId("o_123");
const mid = MenuItemId("m_999");
// 間違い:OrderId が欲しいのに MenuItemId を渡せない
// findOrder(mid);
これだけで、地味に多い事故(「IDの種類違い」)が消えるよ🥹✨
4-2) union + discriminated union で“状態と分岐”を安全に🚦✨
type OrderStatus =
| { kind: "Draft" }
| { kind: "Confirmed" }
| { kind: "Paid" }
| { kind: "Cancelled" };
function assertNever(x: never): never {
throw new Error("Unexpected: " + JSON.stringify(x));
}
function canAddItem(status: OrderStatus): boolean {
switch (status.kind) {
case "Draft":
return true;
case "Confirmed":
case "Paid":
case "Cancelled":
return false;
default:
return assertNever(status); // 追加された状態を見逃さない🧠
}
}
DDDの「状態遷移」は、unionで表すと相性よすぎるよ〜🚦🧡
5) エラーの表現🧯:「例外 vs Result」を混ぜない⚖️
第23章でやったやつの“整理版”だよ✨ ポイントは 層ごとに役割を決めること。
5-1) ドメイン層:ルール違反は“ドメイン例外”にするのが分かりやすい💥
(※ここはチーム方針でResultでもいいけど、混ぜないのが大事!)
class DomainError extends Error {
constructor(message: string) {
super(message);
this.name = "DomainError";
}
}
class OrderAlreadyPaid extends DomainError {
constructor() {
super("支払い済みの注文は変更できません");
this.name = "OrderAlreadyPaid";
}
}
5-2) アプリ層:UI/APIに返すなら Result が相性いい📦✨
type Ok<T> = { ok: true; value: T };
type Err<E> = { ok: false; error: E };
type Result<T, E> = Ok<T> | Err<E>;
type AppError = {
code: "NOT_FOUND" | "INVALID_OPERATION";
userMessage: string;
};
function fail(code: AppError["code"], userMessage: string): Err<AppError> {
return { ok: false, error: { code, userMessage } };
}
5-3) “混ぜる地獄”あるある😵💫
- ドメインで例外投げる
- アプリ層で一部だけResultにして
- さらにどこかで握りつぶす → 呼び出し側が何を信じていいかわからない😇
✅ 対策:
- 「この層は例外」「この層はResult」みたいに境界で変換する✨
6) バリデーションの置き場📍:1回で効く整理🧠✨
同じルールを3回書くの、つらいよね…🥲 だから置き場はこう考えるとスッキリするよ!
- 🖥️ UI:入力補助(すぐ教える、体験を良くする)
- 🎬 アプリ層:ユースケースとしての前提(存在確認など)
- 🏰 ドメイン層:不変条件(絶対ルール) ←ここが最強🔥
例:「数量は1以上」はドメインの不変条件候補📌 例:「未入力なら赤枠」はUIの補助📌
7) 境界とimportの向き🧱➡️:DDDの“崩壊”を防ぐ超重要ポイント
ここが崩れると、DDDは一気に「雰囲気」になるよ😂⚠️
合言葉🗝️✨
- domain は infra を知らない
- app は domain を使う
- infra は domain/app を支える(依存は内側へ向く)
ざっくり構造イメージ📦
domain/:VO, Entity, Aggregate, domain service, repository interfaceapp/:usecase(Application Service), DTO, エラー変換infra/:DB/外部API、Repository実装、Loggerなどtest/:テスト
🧸ミニ練習
- 「domainからinfraをimportしてる箇所」を1つ探して、なぜ危険か説明してみてね🕵️♀️💥
8) DTOの感覚📦:境界で“形を変える”のが大人✨
DDDあるあるで、最初にやりがちなのが 「ドメインの型をそのままAPIレスポンスに返す」やつ😇
でもそれやると…
- ドメイン変更がUI/APIに直撃💥
- 外部に見せたくない情報も混ざる💥
- いつの間にか“表示都合のルール”がドメインに侵入💥
✅ だから、境界でDTOに詰め替える📦✨ (第31章以降、VOが増えるほど効いてくるよ💎)
9) テスト(AAA)🧪:ここまでの設計を“守り続ける”しくみ
AAAの型(超だいじ)✅
- Arrange:準備
- Act:実行
- Assert:確認
例:VitestでAAA🧪
(Vitestは4.0以降の流れでモダンTSの定番寄りだよ)(vitest.dev)
import { describe, it, expect } from "vitest";
describe("Order", () => {
it("支払い済みの注文は変更できない", () => {
// Arrange
const order = /* 支払い済みのOrderを作る */ null as any;
// Act
const act = () => order.addItem("coffee", 1);
// Assert
expect(act).toThrowError();
});
});
テストは「正しいか」を見るだけじゃなくて、 **“ルールを壊さない契約”**として置く感じだよ🧾🧡
10) この章の「最終チェックリスト」✅✨(次章へ行く前に)
自分に○×つけてみてね〜☺️🌸
- 不変って何?を「参照の漏れ」まで含めて説明できる🧊
-
stringのID地獄を、専用型で止められる🪪 - unionで状態を表して、網羅チェック(never)を入れられる🚦
- 例外とResultの役割分担を、層の言葉で説明できる🧯
- domain/app/infra の依存の向きを説明できる🧱➡️
- DTOに詰め替える理由を3つ言える📦
- AAAで最低1本、テストを増やせる🧪
11) 🤖AI活用(Copilot/Codex向け)この章の“勝ちプロンプト”✨
11-1) 不変化レビュー🧊
プロンプト例: 「このクラスの状態が外から破壊されないかレビューして。参照の漏れ(配列/オブジェクト)観点で指摘し、修正案を出して」
11-2) プリミティブ地獄発見🧱
プロンプト例: 「このコードで “string/number が意味を持たずに使われている箇所” を列挙して、専用型(branded type)に置き換える案を出して」
11-3) 例外/Resultの混線チェック🧯
プロンプト例: 「このユースケースの失敗パターンを整理して。ドメイン例外にするもの、Resultで返すもの、ログに残すものを分類して」
11-4) テストケース増殖🧪
プロンプト例: 「この仕様(Given/When/Then)からAAAのテストケースを10個作って。正常系/異常系/境界値をバランス良く」
12) 理解チェック10問📝✨(答えつき)
Q1 🧊
不変(immutable)で一番怖いのは「値の変更」より何? A: 参照が外に漏れて、外側から中身を書き換えられること(配列/オブジェクト)💥
Q2 🧱
プリミティブ地獄の典型例を1つ言って、何が怖いか説明してね。
A: stringでIDを全部扱う → 種類違いのIDを渡しても気づけず、実行時に事故る💥
Q3 🪪
branded typeが嬉しい理由を一言で。 A: “同じ型に見えるもの(string等)”の取り違えをコンパイルで止められる✨
Q4 🚦
discriminated union の嬉しいところは? A: 状態ごとの分岐が安全になり、状態追加時に網羅漏れを検知できる(neverチェック)✨
Q5 🧯
「例外」と「Result」を混ぜると何が起きやすい? A: 呼び出し側がエラー処理方針を決められず、握りつぶしや二重処理が起きる😵💫
Q6 📍
「数量は1以上」はどこに置くのが筋が良い?(UI/アプリ/ドメイン) A: ドメイン(不変条件)🔒
Q7 🧱➡️
domainがinfraをimportしちゃダメな理由を一言で。 A: ドメインが外部都合に汚染され、差し替え・テスト・保守が一気に弱くなる💥
Q8 📦
DTOに詰め替える理由を2つ言ってみて。 A: ドメイン変更の影響を外に漏らさない/外部に不要な情報を出さない(表示都合が侵入しない)✨
Q9 🧪
AAAのA(Arrange/Act/Assert)を日本語で言うと? A: 準備/実行/確認✅
Q10 🎒
次の第31章(VO)に入る前に、あなたのコードで最優先で見直すならどれ?(1つ選んで理由も) A例: 不変性:配列の参照漏れがあると、VO以前に土台が崩れるから🧊
13) 次章へのつなぎ🌸➡️💎
ここまでの武器が固まったら、いよいよ第31章! **Value Object(VO)**はね、今日復習した
- 不変🧊
- 型で意味🏷️
- バリデーションの置き場📍
- テスト🧪
が全部“まとまって効く”から、めちゃ気持ちよく進めるよ〜🥹💎✨
次は「金額Money」「数量Quantity」みたいなVOを作って、仕様の半分を型と生成時チェックで守る体験をしよっか☺️🌸