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