คู่มือนี้ครอบคลุมแนวคิดหลักบางประการเกี่ยวกับสถาปัตยกรรมข้อมูลและ แนวทางปฏิบัติในการจัดโครงสร้างข้อมูล JSON ใน Firebase Realtime Database
การสร้างฐานข้อมูลที่มีโครงสร้างอย่างเหมาะสมต้องอาศัยความรอบคอบพอสมควร สิ่งสำคัญที่สุดคือคุณต้องวางแผน วิธีบันทึกข้อมูล และ ที่ดึงออกมาในภายหลังเพื่อให้กระบวนการดังกล่าวง่ายที่สุด
ข้อมูลมีโครงสร้างอย่างไร: เป็นแผนผัง JSON
ข้อมูล Firebase Realtime Database ทั้งหมดจะจัดเก็บเป็นออบเจ็กต์ JSON ลองนึกถึง
ฐานข้อมูลเป็นแผนผัง JSON ที่โฮสต์บนระบบคลาวด์ ต่างจากฐานข้อมูล SQL ตรงที่ไม่มี
ตารางหรือระเบียน เมื่อคุณเพิ่มข้อมูลลงในโครงสร้าง JSON ข้อมูลนั้นจะกลายเป็นโหนดใน
โครงสร้าง JSON ที่มีอยู่
ด้วยคีย์ที่เชื่อมโยง คุณสามารถใส่คีย์ของตัวเอง
เช่น รหัสผู้ใช้หรือชื่อความหมาย หรืออาจมีการระบุให้คุณโดยใช้
childByAutoId
หากคุณสร้างคีย์ของคุณเอง คีย์ทั้งหมดจะต้องเข้ารหัสแบบ 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": { ... } } }
ตอนนี้คุณสามารถทำซ้ำผ่านรายการห้องแชทได้ด้วยการดาวน์โหลดเฉพาะ 2-3 ไบต์ต่อการสนทนา เรียกข้อมูลเมตาเพื่อแสดงข้อมูลหรือแสดงได้อย่างรวดเร็ว ห้องแชทใน 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
หรือไม่ ดัชนีเร็วขึ้น
และมีประสิทธิภาพมากกว่าการค้นหาหรือสแกนหาข้อมูล