境界を表す型の命名パターン全体マップ(検証・汚染・信頼・解釈・正規化・転送・永続化)
TL;DR
- 境界型の命名は「何が変わる境界か」ごとに定番の語彙セットが存在する。検証(Unvalidated/Validated)はそのうちの1系統に過ぎない。
- 全系統に共通する重心: 「まだ〜でない」側にマークを付け、境界通過後は素の型名(またはドメイン状態名)にする。例外は Tainted 系(歴史的に逆向き: 黒い可能性のある側に印を付ける)。
~Withサフィックスは検証済みマークではなく「必須データが構造的に増えた状態」用(PrismaUserWithPosts、nwiizoPricedOrderWithShipping)。検証境界に流用すると意味論が衝突する。- フロントエンドの
api/境界なら、fetch 直後 =〜Response/Raw〜、transform 後 = ブランド付きドメイン型、の2段の境界語彙が各系統の慣習と整合する。
このドキュメントの射程
- ブランド型 / smart constructor / 幽霊型で「境界を通過済み」であることを型で表すとき、型名をどう付けるのが一般的かのネット調査(英語・日本語)結果。
- 発端は validate 済みの型命名だったが、検証以外の境界(汚染・信頼・解釈・正規化・転送・永続化・ライフサイクル)まで対象を広げて調査した。
境界の種類別・命名パターン
1. 検証境界: Unvalidated〜 / Validated〜(DMMF 系)
『関数型ドメインモデリング』(Scott Wlaschin) 由来。多段の状態遷移があるワークフローで最も参照される。
type Order =
| Unvalidated of UnvalidatedOrder
| Validated of ValidatedOrder
| Priced of PricedOrder
ValidateOrder = UnvalidatedOrder -> Resultのように関数型がワークフローの仕様書になる。- Validated で止まらず、以降は
PricedOrder,PaidOrderとドメイン状態名詞で続ける。検証で情報が増えてもOrderWithPriceとは呼ばない。 - 日本語圏(Zenn / Speaker Deck 勉強会資料)でもこの命名がほぼデファクト。
- 亜種:
Valid〜形容詞前置(ValidUser = Brand)、外部サービス検証済みにはChecked〜(DMMF のCheckedAddress)。
2. 実在確認境界: Unverified〜 / Verified〜
validate = 形式チェック、verify = 実在・所有確認(メール確認済み等)で使い分ける。UnverifiedEmail / VerifiedEmail(Elixir コミュニティの DMMF 読書会など)。
3. 汚染境界: Tainted / Untainted(検証境界の裏返し)
Perl / PHP / Ruby の taint mode: 入力データを untrusted として扱い、taint ラベルを計算に沿って伝播させ、tainted なデータが sensitive な操作に影響できないようにする。tainted? / untaint が言語組み込みの語彙だった(Ruby は 3.0 で削除)。
- Validated 系が「白いことを証明した値に印を付ける」のに対し、Tainted 系は「黒いかもしれない値に印を付ける」。デフォルトの向きが逆。
- 静的型版が Haskell Wiki の幽霊型例:
FormData Unvalidated/sanitise :: FormData a -> FormData Validated。
4. 信頼境界: Safe〜 / Trusted〜 / Sanitized〜(セキュリティ系)
境界の意味が「検証」ではなく「出力先での安全性」の場合の業界標準。プロダクション実績が最も厚い。
- Web 標準 Trusted Types:
TrustedHTML - Google safe-html-types:
SafeHtml(「HTML として描画しても XSS にならない」契約を運ぶ型)、旧世代SanitizedContent - Angular:
SafeHtml,SafeStyle,SafeScript,SafeUrl - W3C 内の命名議論: 「Trusted は不正確では。値の多くは untrusted 由来だが、sink の文脈で安全と分かっているだけ」。名前は「入力の出自」ではなく「出力先での安全性」を表すべきという論点。
5. 解釈境界: Raw〜 / Parsed〜
「バイト列・文字列 → 構造化データ」の境界。未解釈側に Raw を付けるのが業界横断のデファクト。
- Google Cloud API:
RawDocument(生バイトのペイロード)、RawOutput - ASP.NET SignalR:
RawResult(デシリアライズせず生のシリアライズ済みバイトとして保持することを示す型) - "Parse, don't validate" (Alexis King) はこの境界の話。解釈済み側は
Parsed〜か素のドメイン型。
6. 正規化境界: Normalized〜 / Canonical〜
「同じ意味の複数表現 → 標準形」の境界。等価比較・ソートを可能にするための変換。
- ASP.NET Identity:
NormalizedEmail - Rust:
fs::canonicalize(シンボリックリンク解決済みの正規絶対パス)、R:normalizePath - ブランド型例:
AbsolutePath/RelativePath - セキュリティ上は順序が重要:「検証の前に正規化せよ」(CERT FIO16-J、
/cgi-bin/../../../cmd.exe対策)。CanonicalPath型は「この順序を通過済み」の証明として機能する。
7. 転送境界: 〜Dto / 〜Request / 〜Response / 〜Input / 〜View
「ドメイン ⇄ 外界」の境界。命名論争が最も活発で、脱 Dto サフィックスの流れがある。
- Ardalis: 「
Dtoサフィックスは最基本の表現には使えるが、多くの場合はより説明的な名前を。UserDTOではなくCreateUserRequest」 - Ted Young: 「もう
Dtoサフィックスは使わない。表示用はMemberView、受信はCreateMemberRequest」 - 技術用語(Dto)→ 役割名(Request / View / Command)への移行がトレンド。
- フレームワーク規約の例: ServiceStack の
Hello/HelloResponseペア。
8. 永続化境界: New〜 / 〜Draft / Persisted〜
「ID 未採番・未保存 → 保存済み」の境界。
- Microsoft API guidelines の Draft パターン: 必須フィールドが揃う前の部分データを
someRealEntityDraft(nullable プロパティを持つ別エンティティ)として保存し、realizeアクションで本物に変換。 - DDD の「RDB 自動採番を ID に使うな(生成時に ID が手に入らない)」議論が
NewUser(ID なし)/User(ID あり)の型分割の根本原因。Rust Diesel のNewUser(insertable 構造体)が有名な実例。
9. ライフサイクル境界: ドメイン状態名詞
系統1の続き。Priced〜, Paid〜, Shipped〜 など、検証以降の状態はドメイン語彙そのものを型名にする。
~With サフィックスの位置づけ
検証済みマークとしては使われない。 定着した意味は「必須データが構造的に増えた状態」。
| 命名 | 定着している意味 | 境界マークに使う? |
|---|---|---|
XWithY | リレーション・派生データ同梱(Prisma UserWithPosts, StreamWithCount) | ✕ 意味が衝突 |
HasX / MissingX | Rust typestate ビルダーのフィールド充足状態(UserBuilder_HasName_MissingEmail) | △ 構築過程なら |
筋が通る例外は「境界通過で実際に必須データが増える」ケース。nwiizo「型は壁、Rustでもバグを直すな、表現できなくせよ」(関数型まつり2026) では、PricedOrder に Option を足す代わりに shipping: ShippingInfo を必須で持つ PricedOrderWithShipping を新設。状態遷移の主系列は状態名詞、派生的に情報が増えた型は ~With、と同一デッキ内で使い分けが共存している。
全体マップ
| 境界の種類 | 何が変わるか | 未通過側 | 通過側 |
|---|---|---|---|
| 検証 | 保証 | Unvalidated〜, Raw〜 | Validated〜, 制約名型 |
| 実在確認 | 事実確認 | Unverified〜 | Verified〜 |
| 汚染追跡 | 疑い(逆向き) | Tainted | Untainted / 素の型 |
| 信頼 | 出力先での安全性 | 素の string | Safe〜, Trusted〜, Sanitized〜 |
| 解釈 | 構造 | Raw〜 | Parsed〜 / ドメイン型 |
| 正規化 | 表現の一意性 | 素の型 | Normalized〜, Canonical〜 |
| 転送 | レイヤー所属 | 〜Request, 〜Input, 〜Dto | ドメイン型 |
| 永続化 | ID・保存状態 | New〜, 〜Draft | 素の型 / Persisted〜 |
| ライフサイクル | ドメイン状態 | — | Priced〜, Paid〜 等の状態名詞 |
設計指針(結論)
- 命名は「境界の意味」で選ぶ。1つのコードベースに複数の境界があるなら、系統を混ぜずにそれぞれの定番語彙を使う。
- 2値のブランド型 + smart constructor なら、制約名そのものを型名にするのが最有力:
ValidatedEmailではなくEmailAddress(この型の値は常に valid、EmailAddress.make()を通らないと作れない)。"Validated" という語は型名から消え、構造自体が保証になる(Alexis King "Names are not type safety" と同じ立場)。 - フロントエンド
api/境界: fetch 直後 =〜Response/Raw〜→ transform → ブランド付きドメイン型、の2段構え。 ~Withは「データが増えた」専用。境界マークに流用しない。
参考
- Scott Wlaschin, Domain Modeling Made Functional (2018)
- Alexis King, "Parse, don't validate" (2019) / "Names are not type safety" — https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/
- Haskell Wiki: Phantom type — https://wiki.haskell.org/Phantom_type
- W3C trusted-types Issue #7 (Semantics and naming of types) — https://github.com/w3c/trusted-types/issues/7
- Google safe-html-types — https://github.com/google/safe-html-types
- SEI CERT FIO16-J. Canonicalize path names before validating them
- Ardalis, "5 Rules for DTOs" — https://ardalis.com/5-rules-dtos/
- Ted M. Young, "Naming Conventions for DTOs" — https://ted.dev/articles/2021/10/30/naming-conventions-for-dtos/
- Microsoft api-guidelines: Draft entity pattern — https://github.com/microsoft/api-guidelines/pull/464/files
- Prisma Docs: Operating against partial structures of your model types — https://www.prisma.io/docs/orm/prisma-client/type-safety/operating-against-partial-structures-of-model-types
- type-state-builder crate — https://crates.io/crates/type-state-builder
- Ruby Feature #16131: Remove $SAFE, taint and trust — https://bugs.ruby-lang.org/issues/16131
- nwiizo「型は壁、Rustでもバグを直すな、表現できなくせよ」(関数型まつり2026) — https://speakerdeck.com/nwiizo/xing-habi-rustdemobaguwozhi-suna-biao-xian-dekinakuseyo