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