第20章:データ進化①:安全な変更・危険な変更🧬⚖️

この章のゴール🎯
データ(JSONなど)の形を変えるときに、
- ✅ 安全(だいたいOK)
- ⚠️ 注意(条件つき)
- ❌ 危険(破壊変更になりがち)
をサクッと判定できるようになることです🫶✨
20-1. まずは「誰が読む?」で安全度が変わる👀🔁
データの変更って、じつは “読む側(Consumer)” がどれだけ厳しいかで事故りやすさが激変します💥
代表的な2タイプ🧩
- ゆるめ受け取り(寛容):知らない項目が来ても無視して処理できる🙂
- かため受け取り(厳格):知らない項目が来たらエラーにする😠
ここがポイント👇
JSON Schema では、additionalProperties が デフォルトで true(= 基本は「知らないプロパティ来てもOK」寄り)という考え方があります。(Confluent Documentation)
でも現実は、バリデータや実装次第で 厳格にもできちゃうんだよね⚠️
たとえば Zod は、オブジェクトの**未定義キーをデフォルトで strip(落とす)**し、厳格にしたいなら z.strictObject()、ゆるくしたいなら z.looseObject() みたいに切り替えできます。(Zod)
20-2. いちばん大事:安全度の“ざっくりルール”🧠✨
✅ 安全寄り(だいたいOK)
- 任意フィールド(optional)を追加
- 制約をゆるめる(例:
minLength: 3→minLength: 1) - 新しい値を許可する方向に広げる(例:enumに値を追加)※ただし注意点あり⚠️
❌ 危険寄り(破壊変更になりがち)
- 必須フィールド(required)を追加
- フィールド削除(特にレスポンスやイベントpayload)
- 型変更(string ↔ number など)
- 意味変更(同じフィールド名のまま意味だけ変える)
- 制約をきつくする(例:
minLengthを上げる、patternを追加)
このへんは “だいたい破壊” になりやすいので、やるなら移行設計(第21章)で段階的にね🪜⏳
20-3. 安全/危険 早見表📋🧡(OK/注意/NG)
ここでは **「古いクライアントが新しいデータを受け取る」**ケース(後方互換の典型)を中心に見ます🔁 ※「新しいクライアントが古いデータを読む」も同じように考えるよ!
| 変更パターン | 判定 | ひとこと理由 | |
|---|---|---|---|
任意フィールドを追加(例:nickname?: string) | ✅ | 知らない項目を無視できれば壊れにくい🙂 | |
必須フィールドを追加(例:nickname: string) | ❌ | 旧クライアントが未対応で落ちやすい💥 | |
| フィールド削除 | ❌ | 旧クライアントが参照してたら即死😇 | |
| フィールド名変更(rename) | ❌ | 削除+追加と同じ。ほぼ破壊🧨 | |
| 型変更(string→number) | ❌ | パース/表示/計算で事故る🧯 | |
| enumに値を追加 | ⚠️ | switch が default なしだと落ちることある⚠️ | |
| enumから値を削除 | ❌ | 古いデータや古い送信側が送ると詰む😵💫 | |
| 制約をゆるめる | ✅ | 受け取り側が困りにくい🙆♀️ | |
| 制約をきつくする | ⚠️/❌ | “今までOKだったデータ”が突然NGに😱 | |
nullable化(string→`string | null`) | ⚠️ | 取り扱い漏れが出やすい(UI表示とか)💦 |
| 非nullable化(`string | null→string`) | ❌ | 既存データにnullが残ってると崩壊💥 |
20-4. JSON Schemaの“必須/任意”の基本(超重要)📌✨
JSON Schemaは、properties に書いた項目は デフォルトでは必須じゃないです。必須にしたいものだけ required 配列で指定します。(JSON Schema)
そして default は「欠けてたら自動で埋める」動作ではなく、あくまで ヒント(ドキュメントやUI向け)です。(JSON Schema)
だから「追加したけど既定値あるから平気でしょ?」は油断しがち⚠️(埋める処理は自分で書く必要があるよ🧠)
20-5. Zodで体感!安全変更と危険変更を見てみよ🧪✨
例:Userデータ(v1)👤
import * as z from "zod";
export const UserV1 = z.object({
id: z.string(),
name: z.string(),
});
export type UserV1 = z.infer<typeof UserV1>;
✅ ケースA:任意フィールド追加(だいたい安全)➕🙂
export const UserV2 = UserV1.extend({
nickname: z.string().optional(),
});
- 旧クライアントが
idとnameだけ使ってたら、たいてい壊れない✨ - ただし **「知らないキーが来たらエラー」**な受け手だと事故る😱
Zodは、unknown key の扱いを選べます👇(Zod)
// 厳格(知らないキーが来たらエラー)
const Strict = z.strictObject({
id: z.string(),
name: z.string(),
});
// ゆるい(知らないキーも残す)
const Loose = z.looseObject({
id: z.string(),
name: z.string(),
});
「将来フィールド増えるかも」なデータは、受け取り側を厳格にしすぎないのがコツだよ🧁✨
❌ ケースB:必須フィールド追加(危険)➕💥
export const UserV2Bad = UserV1.extend({
nickname: z.string(), // required
});
これ、新しい送信側が nickname を必ず入れる前提だと、旧クライアントが「そんなの知らん!」ってなる可能性大😇
(バリデータや型、UIの想定で落ちます)
❌ ケースC:型変更(ほぼ破壊)🧨
export const UserV2VeryBad = z.object({
id: z.string(),
name: z.number(), // string → number に変更
});
- 表示、検索、ソート、保存…ぜんぶ影響しがち😵💫
- しかも “静かにバグる” 率が高い(最悪)😱
⚠️ ケースD:enumに値追加(注意)🎲
「追加は安全!」って言いたいけど、現場あるある👇
- クライアントが
switchで defaultなし - もしくは「この値しか来ない」前提のUI分岐
で落ちることあるよ⚠️(default / フォールバック大事🛟)
20-6. “完全互換”が難しい世界もある(知っておく話)😵💫🧠
JSON Schema の世界では、open/closed content model(additionalProperties をどうするか)によって「追加/削除の安全度」が変わります。(Confluent Documentation)
さらに、open/closed の両方を同時に満たして “完全互換” をやり切るのが難しいケースがある、という整理もあります。(Yandex Cloud)
なので現実的には👇
- “どっちの互換を優先するか”(後方?前方?)
- 受け手が strict か loose か
- 互換テストを回す(第28〜)
で勝つのが強いよ💪✨
20-7. 安全に育てるための定番テク集🧰🌷
① 追加するときは “optional + 意味が自然なデフォルト” 🧁
- optional にして、無いときも破綻しない設計にする
- ただし default は自動注入じゃないから、必要なら 補完処理を書く(JSON Schema)
② “意味変更” はしない(新フィールドを作る)🚫🧠
statusの意味をコッソリ変える…は事故率MAX😇- 新しい意味は
statusV2とか、別名で追加して段階移行が安全🪜
③ rename は「旧→新の併存期間」を作る🪄
- 旧フィールドを残しつつ、新フィールドも追加
- 送信側は両方出す期間を作って、受信側が移行したら旧を消す(第21章へ)⏳
④ unknown key に対する方針を決める🎭
- “増えうるデータ” は、受け取り側で strictにしすぎない
- Zodなら
strict/looseを明示してチームで統一しよう🧁(Zod)
20-8. ミニ演習✍️📊(OK/注意/NGに分類しよう!)
次の変更を ✅OK / ⚠️注意 / ❌NG に分けてみてね😊✨
userにavatarUrl?: stringを追加userにemail: string(必須)を追加user.nameを削除user.age: numberをstringに変更user.roleの enum にadminを追加user.roleの enum からguestを削除user.nickname?: stringをnickname: string(必須)に変更user.bioのmaxLength: 160をmaxLength: 80に変更user.bioのmaxLengthを削除(制約ゆるめ)user.statusの意味を「有効/無効」から「課金/無料」に変更(名前は同じ)
こたえ合わせ🎀
- ✅ 2) ❌ 3) ❌ 4) ❌ 5) ⚠️ 6) ❌ 7) ❌ 8) ⚠️/❌ 9) ✅ 10) ❌
20-9. AI活用プロンプト集🤖💗(コピペOK)
変更の安全度チェック🔍
- 「この JSON の変更(diff貼るね)が、後方互換/前方互換のどちらで危険?理由も3つで教えて」
- 「この変更を ✅/⚠️/❌ に分類して、事故る具体例も1つずつ作って」
“安全な代替案”を出してもらう🧠🪄
- 「この破壊変更を、段階移行できる設計に直して(旧新併存→移行→削除の3段で)」
- 「rename を安全にやるためのフィールド設計と運用手順を提案して」
テスト観点を作る🧪
- 「旧スキーマで新データをパースするテストケースを10個作って(境界値も)」
- 「Zodで互換性テストを書くなら、どんな
safeParseテストが必要?」
20-10. 章末チェックリスト✅🧡
リリース前にこれだけ確認しよう✨
- 変更は 追加中心になってる?(削除・型変更してない?)
- 必須追加してない?(やるなら段階移行)
- enum追加は default/fallback がある?
- バリデーションは strict/loose 方針が統一されてる?(Zod)
- defaultに頼りすぎてない?(自動注入じゃない)(JSON Schema)
- スキーマの互換性を “バージョン管理してチェック” できる仕組みがある?(Schema Registry等)(Confluent Documentation)
今日のミニまとめ🌸
**データ進化は「追加は比較的安全、削除/型変更/意味変更は危険」**が基本ルール💡 でも実戦では 受け手の strict/loose と 互換テスト が勝敗を分けるよ🧁✨