คู่มือนี้ครอบคลุมแนวคิดหลักบางส่วนในสถาปัตยกรรมข้อมูลและแนวทางปฏิบัติแนะนำ สำหรับการจัดโครงสร้างข้อมูล JSON ใน Firebase Realtime Database
การสร้างฐานข้อมูลที่มีโครงสร้างที่เหมาะสมต้องมีการวางแผนล่วงหน้าพอสมควร ที่สำคัญที่สุดคือคุณต้องวางแผนวิธีบันทึกข้อมูลและ เรียกข้อมูลในภายหลังเพื่อให้กระบวนการดังกล่าวง่ายที่สุด
โครงสร้างของข้อมูล: เป็นทรี JSON
ระบบจะจัดเก็บข้อมูล Firebase Realtime Database ทั้งหมดเป็นออบเจ็กต์ JSON คุณอาจนึกถึง
ฐานข้อมูลเป็นโครงสร้าง JSON ที่โฮสต์ในระบบคลาวด์ ซึ่งแตกต่างจากฐานข้อมูล SQL ที่ไม่มีตารางหรือระเบียน เมื่อเพิ่มข้อมูลลงในแผนผัง JSON ข้อมูลนั้นจะกลายเป็นโหนดใน
โครงสร้าง JSON ที่มีอยู่พร้อมคีย์ที่เชื่อมโยง คุณสามารถระบุคีย์ของคุณเอง เช่น รหัสผู้ใช้หรือชื่อเชิงความหมาย หรือจะให้เราจัดหาให้โดยใช้ push()
ก็ได้
หากสร้างคีย์ด้วยตนเอง คีย์ดังกล่าวต้องเข้ารหัส UTF-8 มีขนาดสูงสุด 768 ไบต์ และต้องไม่มี .
, $
, #
, [
, ]
, /
หรืออักขระควบคุม ASCII 0-31 หรือ 127 คุณยังใช้อักขระควบคุม ASCII ในค่า
เองไม่ได้ด้วย
ตัวอย่างเช่น ลองพิจารณาแอปพลิเคชันแชทที่อนุญาตให้ผู้ใช้จัดเก็บโปรไฟล์พื้นฐานและรายชื่อติดต่อ
โดยปกติแล้ว โปรไฟล์ผู้ใช้จะอยู่ที่เส้นทาง เช่น
/users/$uid
ผู้ใช้ alovelace
อาจมีรายการฐานข้อมูลที่
มีลักษณะดังนี้
{ "users": { "alovelace": { "name": "Ada Lovelace", "contacts": { "ghopper": true }, }, "ghopper": { "..." }, "eclarke": { "..." } } }
แม้ว่าฐานข้อมูลจะใช้ทรี JSON แต่ข้อมูลที่จัดเก็บไว้ในฐานข้อมูลสามารถแสดงเป็นประเภทดั้งเดิมบางประเภทที่สอดคล้องกับประเภท JSON ที่พร้อมใช้งานเพื่อช่วยให้คุณเขียนโค้ดที่บำรุงรักษาได้มากขึ้น
แนวทางปฏิบัติแนะนำสำหรับโครงสร้างข้อมูล
หลีกเลี่ยงการซ้อนข้อมูล
เนื่องจาก Firebase Realtime Database อนุญาตให้ซ้อนข้อมูลได้ลึกสูงสุด 32 ระดับ คุณจึงอาจคิดว่าโครงสร้างนี้ควรเป็นโครงสร้างเริ่มต้น อย่างไรก็ตาม เมื่อดึงข้อมูลที่ตำแหน่งในฐานข้อมูล คุณจะดึง โหนดลูกทั้งหมดของตำแหน่งนั้นด้วย นอกจากนี้ เมื่อให้สิทธิ์อ่านหรือเขียนแก่บุคคลอื่น ที่โหนดในฐานข้อมูล คุณยังให้สิทธิ์เข้าถึงข้อมูลทั้งหมดภายใต้โหนดนั้นด้วย ดังนั้น ในทางปฏิบัติ คุณควรทำให้โครงสร้างข้อมูลแบนราบที่สุด เท่าที่จะเป็นไปได้
ตัวอย่างที่แสดงให้เห็นว่าทำไมข้อมูลที่ซ้อนกันจึงไม่ดี ให้พิจารณาโครงสร้างที่มีการซ้อนกันหลายชั้นต่อไปนี้
{ // This is a poorly nested data architecture, because iterating the children // of the "chats" node to get a list of conversation titles requires // potentially downloading hundreds of megabytes of messages "chats": { "one": { "title": "Historical Tech Pioneers", "messages": { "m1": { "sender": "ghopper", "message": "Relay malfunction found. Cause: moth." }, "m2": { ... }, // a very long list of messages } }, "two": { "..." } } }
การออกแบบที่ซ้อนกันนี้ทำให้การวนซ้ำผ่านข้อมูลกลายเป็นปัญหา ตัวอย่างเช่น การแสดงชื่อของการสนทนาในแชทต้องดาวน์โหลดchats
ทั้งหมด รวมถึงสมาชิกและข้อความทั้งหมด ไปยังไคลเอ็นต์
รวมโครงสร้างข้อมูล
หากข้อมูลแยกเป็นเส้นทางต่างๆ หรือที่เรียกว่าการลดรูปแบบปกติ คุณจะดาวน์โหลดข้อมูลได้อย่างมีประสิทธิภาพในการเรียกใช้แยกกันตามที่ต้องการ พิจารณา โครงสร้างที่ลดลำดับชั้นนี้
{ // Chats contains only meta info about each conversation // stored under the chats's unique ID "chats": { "one": { "title": "Historical Tech Pioneers", "lastMessage": "ghopper: Relay malfunction found. Cause: moth.", "timestamp": 1459361875666 }, "two": { "..." }, "three": { "..." } }, // Conversation members are easily accessible // and stored by chat conversation ID "members": { // we'll talk about indices like this below "one": { "ghopper": true, "alovelace": true, "eclarke": true }, "two": { "..." }, "three": { "..." } }, // Messages are separate from data we may want to iterate quickly // but still easily paginated and queried, and organized by chat // conversation ID "messages": { "one": { "m1": { "name": "eclarke", "message": "The relay seems to be malfunctioning.", "timestamp": 1459361875337 }, "m2": { "..." }, "m3": { "..." } }, "two": { "..." }, "three": { "..." } } }
ตอนนี้คุณสามารถวนซ้ำในรายการห้องได้โดยการดาวน์โหลดเพียงไม่กี่ไบต์ต่อการสนทนา ซึ่งจะช่วยให้ดึงข้อมูลเมตาได้อย่างรวดเร็วเพื่อแสดงหรือแสดงห้องใน UI โดยสามารถดึงข้อมูลข้อความแยกกันและแสดงเมื่อได้รับ เพื่อให้ UI ตอบสนองและรวดเร็วอยู่เสมอ
สร้างข้อมูลที่ปรับขนาดได้
เมื่อสร้างแอป การดาวน์โหลดชุดย่อยของรายการมักจะดีกว่า ซึ่งมักเกิดขึ้นหากรายการมีหลายพันระเบียน เมื่อความสัมพันธ์นี้เป็นแบบคงที่และทิศทางเดียว คุณก็เพียงแค่ซ้อน ออบเจ็กต์ย่อยไว้ใต้ออบเจ็กต์หลัก
บางครั้งความสัมพันธ์นี้อาจมีความเปลี่ยนแปลงมากขึ้น หรืออาจจำเป็นต้อง ยกเลิกการทำให้ข้อมูลนี้เป็นปกติ ในหลายๆ ครั้ง คุณสามารถยกเลิกการทำให้ข้อมูลเป็นปกติได้โดยใช้คําค้นหา เพื่อดึงข้อมูลชุดย่อยตามที่อธิบายไว้ในส่วนดึงข้อมูล
แต่การดำเนินการนี้อาจยังไม่เพียงพอ ตัวอย่างเช่น พิจารณาความสัมพันธ์แบบ 2 ทาง ระหว่างผู้ใช้กับกลุ่ม ผู้ใช้จะอยู่ในกลุ่มได้ และกลุ่มประกอบด้วย รายชื่อผู้ใช้ เมื่อถึงเวลาที่ต้องตัดสินใจว่าผู้ใช้ควรอยู่ในกลุ่มใด เรื่องราวก็ซับซ้อนขึ้น
สิ่งที่เราต้องการคือวิธีที่ยอดเยี่ยมในการแสดงรายการกลุ่มที่ผู้ใช้เป็นสมาชิกและ ดึงข้อมูลเฉพาะสำหรับกลุ่มเหล่านั้น ดัชนีของกลุ่มจะช่วยได้มากในกรณีต่อไปนี้
// An index to track Ada's memberships { "users": { "alovelace": { "name": "Ada Lovelace", // Index Ada's groups in her profile "groups": { // the value here doesn't matter, just that the key exists "techpioneers": true, "womentechmakers": true } }, // ... }, "groups": { "techpioneers": { "name": "Historical Tech Pioneers", "members": { "alovelace": true, "ghopper": true, "eclarke": true } }, // ... } }
คุณอาจสังเกตเห็นว่าการดำเนินการนี้จะทำซ้ำข้อมูลบางอย่างโดยจัดเก็บความสัมพันธ์
ทั้งในบันทึกของ Ada และในกลุ่ม ตอนนี้ alovelace
ได้รับการจัดทำดัชนีภายใต้กลุ่ม และ techpioneers
ปรากฏอยู่ในโปรไฟล์ของ Ada ดังนั้นหากต้องการลบ Ada
ออกจากกลุ่ม คุณต้องอัปเดตใน 2 ที่
ซึ่งเป็นความซ้ำซ้อนที่จำเป็นสำหรับความสัมพันธ์แบบ 2 ทาง ซึ่งช่วยให้คุณดึงข้อมูลการเป็นสมาชิกของ Ada ได้อย่างรวดเร็วและมีประสิทธิภาพ แม้ว่ารายชื่อผู้ใช้หรือกลุ่มจะขยายเป็นหลายล้าน หรือเมื่อRealtime Databaseกฎความปลอดภัย ป้องกันการเข้าถึงบางระเบียน
วิธีนี้จะกลับด้านข้อมูลโดยการแสดงรหัสเป็นคีย์และตั้งค่า
เป็นจริง ซึ่งจะทำให้การตรวจสอบคีย์ง่ายเพียงแค่การอ่าน /users/$uid/groups/$group_id
และตรวจสอบว่าคีย์นั้นเป็น null
หรือไม่ ดัชนีจะเร็วกว่า
และมีประสิทธิภาพมากกว่าการค้นหาหรือสแกนข้อมูล