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

15章:外部データを信用しない② バリデーション(検証)の設計 ✅🧪

15-0. バリデーションって何?なんでACLでやるの?🛡️✨

外部APIのデータは、ざっくり言うと 「他人の家の荷物」 です📦 中身が壊れてたり、説明と違ったり、突然ラベルが変わったりします😇

そこでACLでは、内側(ドメイン)に入れる前に“検品” します👀✅ これがバリデーション(検証)です!

  • ACLのバリデーション:外部のクセ・事故データを入口で止める🚧

* ドメインの不変条件(第8章):アプリのルールを最後まで守り切る🔒

この2段構えがあると最強です💪✨

イメージ:バリデーションという検品所


15-1. 何をチェックする?(チェック項目カタログ)🧾✅

外部データの検証は、だいたい次の4カテゴリに分けると迷いません🧠✨

A) 必須チェック(存在する?)🧱

  • 必須フィールドが あるか
  • null が来ていいのか(ダメなら弾く🙅‍♀️)

例:studentId が無い学生データは、もう学生じゃない…😇

B) 型・形式チェック(見た目が正しい?)🔤

  • 文字列?数値?配列?オブジェクト?
  • 日時形式(ISOっぽい?)⏰
  • メール形式、UUID形式、固定桁数など📧

C) 範囲・長さチェック(常識の範囲?)📏

  • 数値の範囲:0以上上限
  • 文字列長:1〜50文字 など
  • 配列長:0〜100件 など

D) 値の集合チェック(許可された値?)🧩

  • enum / 区分値(例:"1"|"2" だけOK)
  • ステータス("OK"|"NG"|"PENDING" だけOK)

外部APIで一番事故るの、ここです💥 「知らない区分値」が急に増えます😇


15-2. 失敗したらどうする?(3つの方針)🚦

バリデーション失敗時の扱いは、先に方針を決めるのが設計です✍️✨ おすすめは次の3パターンを状況で使い分けること👇

イメージ:バリデーション失敗時の3つの進路

方針①:落とす(エラーで止める)🧯❌

  • お金・決済・本人確認など 重要データ は基本これ💰
  • “間違った成功”が一番怖い😱

方針②:代替値にする(安全なデフォルト)🧸

  • 表示用データなどで、多少の欠損が許せる場合
  • 例:nickname が欠けてたら "(unknown)" にする

⚠️ただし! 代替値が ドメインの意味を変える なら危険です☠️(例:残高が欠けたから0円扱い、は事故🔥)

方針③:保留・隔離(Quarantine)🧊📦

  • 使えないデータを 「隔離してログに残す」
  • 後で原因調査できるし、外部仕様変更に強い💪

15-3. 「パース」と「バリデーション」を分けると超ラク🧼➡️✅

第14章の“整形(パース)”と、第15章の“検証(バリデーション)”は、分けると頭が整理しやすいです🧠✨

  • パース:型変換・トリム・全角半角・文字→数値 など(整える)🧽
  • バリデーション:必須・範囲・形式・許可値(正しいか確かめる)✅

この順番が気持ちいい👇 整える → 確かめる → ドメイン型にする 🎯


15-4. 2026年の定番:スキーマバリデーションで入口を固める🧱✨

TypeScript界隈で「unknownなJSONを型安全にする」用途だと、スキーマバリデーションが超定番です📦✅ 2026年1月時点でも、Zodは利用者が多く、最新版はv4系です(npmの最新表示)(npm) (TypeScript自体も 5.9.x が最新リリースとして案内されています)(GitHub)

ここでは例として Zod を使います🧡 ※軽量志向なら Valibot も選択肢(npm上で最新版が確認できます)(npm)


15-5. 実装の基本形(safeParseで“失敗を値として扱う”)🧪✨

Zodの基本はこれです👇

  • safeParse:成功/失敗を 例外じゃなく戻り値 で受け取れる(扱いやすい!)🙌
import { z } from "zod";

// 外部から来る「整形後データ」を想定(第14章でパース済み、という気持ち)
const StudentDtoSchema = z.object({
stu_id: z.string().min(1).max(20).regex(/^[A-Z0-9_-]+$/),
stu_kbn: z.string().regex(/^(1|2)$/), // 例:1=学部, 2=院
point: z.number().int().min(0).max(999_999),
updated_at: z.string().datetime({ offset: true }), // ISO + タイムゾーン込み想定
mail: z.string().email().optional(),
});

export type StudentDto = z.infer<typeof StudentDtoSchema>;

export function validateStudentDto(input: unknown) {
const r = StudentDtoSchema.safeParse(input);
if (!r.success) {
return {
ok: false as const,
error: {
kind: "AclValidationError" as const,
source: "StudentApi" as const,
issues: r.error.issues.map((i) => ({
path: i.path.join("."),
code: i.code,
message: i.message,
})),
},
};
}
return { ok: true as const, value: r.data };
}

ポイントはここ👇✨

  • 入力は unknown で受ける(外部は信用しない宣言🛡️)
  • 失敗をthrowしない(後で「落とす/隔離/代替値」の方針に分岐しやすい🚦)

15-6. “境界で落とす”が効く理由(内側が平和になる)🕊️✨

バリデーションをACLでやらないと、内側がこうなります👇😵‍💫

  • 画面・ユースケース・ドメインのあちこちに if (!x) return ... が散らばる
  • どこが入口で、どこが例外なのか不明
  • テストも地獄🧪💦

ACLで入口を固めると👇😊

  • 内側は「正しい前提」でコードが書ける
  • ドメインのルール(不変条件)だけに集中できる
  • “外部の事故”はACLだけ見れば追える🔍

15-7. 失敗時の3方針をコードに落とす(例)🚦💡

① 落とす(エラーで止める)🧯

const v = validateStudentDto(raw);
if (!v.ok) {
// ここでログ&エラー返却(第16〜17章で「翻訳されたエラー」にする)
throw new Error(`Invalid external payload: ${JSON.stringify(v.error.issues)}`);
}

② 代替値(安全なデフォルト)🧸

const v = validateStudentDto(raw);
if (!v.ok) {
// mailだけが無くてもOK、みたいな設計なら
// ただし「意味が変わる代替」はNGだよ⚠️
return { ok: true as const, value: { /* ここで安全なデフォルト */ } };
}

③ 隔離(Quarantine)🧊📦

const v = validateStudentDto(raw);
if (!v.ok) {
// 「外部が変わった」証拠を残すのが大事!
console.warn("[ACL] Quarantine payload", { issues: v.error.issues, raw });
return { ok: false as const, error: v.error };
}

15-8. ありがちな“検証の落とし穴”5選 😇🪤

  1. 「必須だけ」チェックして安心する(範囲/許可値が抜けがち)
  2. 代替値でごまかしすぎる(サイレント事故の元🔥)
  3. ログが無い(外部変更に気づけない🚨)
  4. 検証ルールが散る(ACLの中に“ひとまとめ”が基本🧱)
  5. 失敗を例外にしすぎる(制御が読みにくい😵)

15-9. ミニ演習:検証ルールを追加してみよう🎓✨

次のルールを StudentDtoSchema に追加してみてね👇💪

  • stu_idA から始まる(例:A00123)🪪
  • point は 0〜100000 の範囲に縮める(上限を現実に合わせる)📏
  • updated_at は「未来日」だったら隔離(時計ズレ対策)⏳🧊

ヒント:

  • 形式チェックは regex、複合条件は refine が便利だよ✨

15-10. AI活用コーナー:バリデーションは“下書き生成”が相性最高🤖🧡

バリデーションは「型・範囲・形式」の列挙が多いので、AIが得意です✍️✨ ただし 最終判断は人間が監督 ね🛡️

使える指示例(コピペOK)📋

  • 「このJSONサンプルからZodスキーマを作って。必須/optionalも推測して」
  • 「このAPIの“事故りそうポイント”を挙げて、検証ルール案を10個出して」
  • 「safeParse失敗時のエラーを、path付きで整形して返す関数を書いて」

⚠️注意:外部レスポンスに個人情報が含まれる場合は、マスキングしてから貼ろうね🔒


15-11. まとめ:第15章で持ち帰ること🎒✨

  • 外部データは unknown として扱う🛡️
  • ACLで 入口検証 をして、内側を平和にする🕊️
  • 失敗時の方針は3つ:落とす / 代替 / 隔離 🚦
  • スキーマバリデーション(例:Zod)で ルールを1箇所に集約 🧱✅

次章(第16章)では、ここで出た「失敗」を 外部エラーとドメインエラーに分ける ところに進むよ🧊🔥