คู่มือนี้ครอบคลุมแนวคิดหลักบางส่วนในสถาปัตยกรรมข้อมูลและแนวทางปฏิบัติแนะนำ สำหรับการจัดโครงสร้างข้อมูล 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
หรือไม่ ดัชนีจะเร็วกว่า
และมีประสิทธิภาพมากกว่าการค้นหาหรือสแกนข้อมูล