Schema validation
MongoDB เป็นแบบ schema-flexible โดยปริยาย: collection จะยอมรับ document ที่มีรูปร่างต่างกันอย่างมากอย่างเต็มใจ ซึ่งให้อิสระระหว่างการ prototype และอันตรายใน production Schema validation ให้คุณเลือกกลับเข้าสู่การบังคับใช้ — เพื่อประกาศ สำหรับ collection หนึ่ง ว่า field ใดต้องมี ต้องเป็นชนิดใด และค่าใดที่อนุญาต — โดยไม่ต้องทิ้งความยืดหยุ่นของ document model ในจุดที่ยังต้องใช้ คุณแนบกฎผ่าน $jsonSchema validator แล้ว server จะตรวจทุกการเขียนเทียบกับกฎนั้น ถือเป็นราวกันตกที่ค่อยเพิ่มทีหลังตอนรูปร่างของ collection ลงตัวแล้ว
validator หน้าตาเป็นอย่างไร
หัวข้อที่มีชื่อว่า “validator หน้าตาเป็นอย่างไร”validator คือ query expression ที่ทุก document ต้องเป็นไปตาม operator $jsonSchema เป็นวิธีมาตรฐานในการเขียน สร้างขึ้นจาก keyword หลักสามตัว:
bsonType— ชนิดที่ field (หรือ document) ต้องเป็น เช่น"object","string","int"หรือ"array"required— array ของชื่อ field ที่ต้องมีอยู่properties— map ต่อ field ที่อธิบายชนิดและข้อจำกัดของแต่ละ field
นี่คือ validator สำหรับ members collection: สมาชิกทุกคนต้องมี name เป็น string และปี joined เป็น integer ที่อย่างน้อย 1900 และถ้ามี fines ก็ต้องเป็นตัวเลขที่ไม่ติดลบ
flowchart LR
W["Incoming write"] --> V{"Matches $jsonSchema?"}
V -->|"Yes"| A["Accepted and stored"]
V -->|"No, action error"| R["Rejected with a validation error"]
V -->|"No, action warn"| L["Stored but logged as a warning"] การสร้าง collection พร้อม validator
หัวข้อที่มีชื่อว่า “การสร้าง collection พร้อม validator”การแนบ validator เป็นส่วนหนึ่งของการสร้าง collection การเรียกด้านล่างสร้าง members ด้วยกฎที่อธิบายไว้ข้างต้น นี่เป็น operation จริง จึงดูแตกต่างกันเล็กน้อยใน shell และในแต่ละ driver:
db.createCollection("members", { validator: { $jsonSchema: { bsonType: "object", required: ["name", "joined"], properties: { name: { bsonType: "string" }, joined: { bsonType: "int", minimum: 1900 }, fines: { bsonType: "number", minimum: 0 } } } }, validationLevel: "strict", validationAction: "error"})await db.createCollection("members", { validator: { $jsonSchema: { bsonType: "object", required: ["name", "joined"], properties: { name: { bsonType: "string" }, joined: { bsonType: "int", minimum: 1900 }, fines: { bsonType: "number", minimum: 0 }, }, }, }, validationLevel: "strict", validationAction: "error",});db.create_collection( "members", validator={ "$jsonSchema": { "bsonType": "object", "required": ["name", "joined"], "properties": { "name": {"bsonType": "string"}, "joined": {"bsonType": "int", "minimum": 1900}, "fines": {"bsonType": "number", "minimum": 0}, }, } }, validationLevel="strict", validationAction="error",)schema := bson.M{ "$jsonSchema": bson.M{ "bsonType": "object", "required": bson.A{"name", "joined"}, "properties": bson.M{ "name": bson.M{"bsonType": "string"}, "joined": bson.M{"bsonType": "int", "minimum": 1900}, "fines": bson.M{"bsonType": "number", "minimum": 0}, }, },}opts := options.CreateCollection(). SetValidator(schema). SetValidationLevel("strict"). SetValidationAction("error")err := db.CreateCollection(ctx, "members", opts)if err != nil { return err}let validator = doc! { "$jsonSchema": { "bsonType": "object", "required": ["name", "joined"], "properties": { "name": { "bsonType": "string" }, "joined": { "bsonType": "int", "minimum": 1900 }, "fines": { "bsonType": "number", "minimum": 0 } } }};db.create_collection("members") .validator(validator) .validation_level(ValidationLevel::Strict) .validation_action(ValidationAction::Error) .await?;document ที่ผ่านครบทุกกฎจะบันทึกลงไปได้เลย ไม่มีเสียงบ่น:
{ "name": "Ada", "joined": 2024, "fines": 0 }ส่วน document ที่ผิดกฎจะโดน reject อันนี้ขาด field joined ที่ required และใส่ name ผิด type การเขียนจึงล้มเหลวด้วย validation error แทนที่จะบันทึกเงียบ ๆ:
{ "name": 42 }validationLevel และ validationAction
หัวข้อที่มีชื่อว่า “validationLevel และ validationAction”มีปุ่มสองตัวควบคุมว่ากฎจะกัดแรงแค่ไหน validationLevel ตัดสินว่าจะตรวจ การเขียนแบบไหนบ้าง: "strict" (ค่าปริยาย) validate ทุก insert และ update ส่วน "moderate" validate insert และ update ต่อ document ที่เป็นไปตาม schema อยู่แล้ว แต่ปล่อย document ที่ invalid เดิมไว้เฉย ๆ — มีประโยชน์เมื่อคุณเพิ่ม validation ให้กับ collection ที่มีข้อมูลรกอยู่แล้ว validationAction ตัดสินว่าจะเกิดอะไรขึ้นเมื่อล้มเหลว: "error" (ค่าปริยาย) ปฏิเสธการเขียน ส่วน "warn" อนุญาตให้เขียนแต่บันทึก warning ไว้ใน server log — วิธีอ่อนโยนในการค้นหาการละเมิดโดยไม่ทำให้การเขียนพังระหว่างการ migration
การ rollout ที่พบบ่อยคือเริ่มด้วย validationAction: "warn" และ validationLevel: "moderate" เฝ้าดู log เพื่อเรียนรู้ว่าข้อมูลเดิมละเมิดกฎใหม่มากแค่ไหน แก้ไขตัวที่ผิด แล้วค่อยเข้มขึ้นเป็น "error" และ "strict"
ใน Compass: เปิด collection แล้วไปที่แท็บ Validation คุณสามารถวางหรือแก้ไข document $jsonSchema ที่นั่น เลือก validation level และ action จาก dropdown และ Compass จะแสดงตัวอย่างว่า document เดิมตัวใดจะผ่านหรือไม่ผ่านก่อนที่คุณจะบันทึกกฎ — วิธีปลอดภัยในการทดสอบ validator กับข้อมูลจริง
เคล็ดลับและข้อควรระวัง
หัวข้อที่มีชื่อว่า “เคล็ดลับและข้อควรระวัง”- Validation เป็นแบบ opt-in และต่อ collection collection ที่ไม่มี validator ทำงานเหมือนเดิมทุกประการ — ไม่มีการบังคับใช้อะไร
bsonTypeเข้มงวดกว่าการจัดการตัวเลขแบบหลวม ๆ ของ JSON:"int","long","double"และ"decimal"แยกจากกัน ถ้า driver ส่งค่าเป็น double ในที่ที่คุณ required เป็น int การเขียนจะล้มเหลว — เป็นเรื่องที่ทำให้แปลกใจกันบ่อยrequiredตรวจแค่ว่า field มีอยู่ เท่านั้น ส่วนกฎราย field ในpropertiesตรวจ type และค่า ใช้ทั้งสองด้วยกันเพื่อปักหมุด field ให้แน่นสมบูรณ์- ใช้
"moderate"บวก"warn"เมื่อเพิ่ม validation ให้กับ collection ที่มีอยู่ เพื่อที่คุณจะไม่ทำให้การเขียนต่อ document ที่มีมาก่อนกฎพัง - คุณสามารถเปลี่ยนหรือลบ validator ทีหลังด้วยคำสั่ง
collMod— validation ไม่ใช่ประตูทางเดียว
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
ใช้ $jsonSchema validation | จับการเขียนที่ผิดรูปแบบได้ตั้งแต่ระดับ database เหมือนมี schema แบบเบา ๆ คอยกันตก | เพิ่มขั้นตอนตอนเขียน ต้องคอย sync กฎกับโค้ดฝั่งแอปพลิเคชันเสมอ |
| ไม่ใช้ validator (พึ่ง schema flexibility เดิมของ MongoDB) | ยืดหยุ่นเต็มที่ เขียนอะไรก็ได้โดยไม่มีแรงเสียดทาน | ข้อมูลผิดรูปแบบหลุดเข้า production ได้ง่าย กว่าจะรู้ก็สายเกินไปแล้ว |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- คิดว่า
$jsonSchemavalidator แทนที่การ validate ฝั่งแอปพลิเคชันได้ทั้งหมด — validator เช็คแค่ shape พื้นฐานตอนเขียนเข้า database เท่านั้น ไม่รู้ business rule เชิงลึก เช่น “email นี้ต้องไม่ซ้ำกับ user อื่น” ยังต้องใช้ unique index หรือ logic แยกต่างหาก - เพิ่ม validator เข้ม ๆ ทันทีบน collection ที่มีข้อมูลเก่าอยู่แล้ว — ถ้าไม่ผ่าน
"moderate"กับ"warn"ก่อน การเปิด"strict"กับ"error"ทันทีจะทำให้การเขียนต่อ document เก่าที่ไม่ตรง schema ล้มเหลวโดยไม่ทันตั้งตัว - ลืมอัปเดต validator เมื่อ schema ของแอปพลิเคชันเปลี่ยน — validator ไม่ sync กับโค้ดอัตโนมัติ ถ้าเพิ่ม field ใหม่ในแอปแต่ลืมแก้
$jsonSchemaการเขียนอาจถูกปฏิเสธโดยไม่คาดคิด
💡 ตัวอย่างจากของจริง
Toyota Connected — validate payload telemetry จากรถยนต์ที่เชื่อมต่อด้วย
$jsonSchemaก่อนเก็บ เพื่อกัน sensor ที่ส่งข้อมูลผิดรูปแบบไม่ให้ปนเข้าไปใน collection หลักระบบธนาคารและ fintech — ใช้
$jsonSchemaเป็นราวกันตกบน collection ธุรกรรม กำหนด required field อย่างจำนวนเงินและสกุลเงินให้ตรง type เสมอ ก่อนที่ข้อมูลผิดจะไหลต่อไปยัง downstream analytics