คู่มือนี้ครอบคลุมแนวคิดหลักบางประการในสถาปัตยกรรมข้อมูลและแนวทางปฏิบัติที่ดีที่สุดสำหรับการจัดโครงสร้างข้อมูล JSON ในฐานข้อมูลเรียลไทม์ของ Firebase
การสร้างฐานข้อมูลที่มีโครงสร้างอย่างถูกต้องนั้นต้องใช้ความรอบคอบไม่น้อย สิ่งสำคัญที่สุดคือ คุณต้องวางแผนว่าจะบันทึกข้อมูลอย่างไรและดึงข้อมูลในภายหลังเพื่อให้กระบวนการนั้นง่ายที่สุด
โครงสร้างข้อมูลเป็นอย่างไร: เป็นต้นไม้ JSON
ข้อมูลฐานข้อมูลเรียลไทม์ของ Firebase ทั้งหมดถูกจัดเก็บเป็นวัตถุ JSON คุณสามารถคิดว่าฐานข้อมูลเป็นแผนผัง JSON ที่โฮสต์บนคลาวด์ ไม่มีตารางหรือระเบียนต่างจากฐานข้อมูล SQL เมื่อคุณเพิ่มข้อมูลลงในแผนผัง JSON ข้อมูลนั้นจะกลายเป็นโหนดในโครงสร้าง JSON ที่มีอยู่ซึ่งมีคีย์ที่เกี่ยวข้อง คุณสามารถระบุคีย์ของคุณเองได้ เช่น ID ผู้ใช้หรือชื่อเชิงความหมาย หรือให้รหัสแก่คุณโดยใช้ 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": { ... }
}
}
ตอนนี้สามารถวนซ้ำผ่านรายการห้องโดยการดาวน์โหลดเพียงไม่กี่ไบต์ต่อการสนทนา ดึงข้อมูลเมตาอย่างรวดเร็วสำหรับการลงรายการหรือแสดงห้องใน UI สามารถดึงข้อความแยกต่างหากและแสดงเมื่อมาถึง ทำให้ UI ตอบสนองและรวดเร็ว
สร้างข้อมูลที่ปรับขนาด
เมื่อสร้างแอป การดาวน์โหลดชุดย่อยของรายการมักจะดีกว่า ซึ่งเป็นเรื่องปกติโดยเฉพาะอย่างยิ่งหากรายการมีหลายพันระเบียน เมื่อความสัมพันธ์นี้เป็นแบบคงที่และเป็นทิศทางเดียว คุณสามารถซ้อนวัตถุลูกไว้ใต้พาเรนต์ได้
บางครั้ง ความสัมพันธ์นี้มีไดนามิกมากกว่า หรืออาจจำเป็นต้องลดมาตรฐานข้อมูลนี้ หลายครั้ง คุณสามารถทำให้ข้อมูลปกติได้โดยใช้แบบสอบถามเพื่อดึงข้อมูลบางส่วน ตามที่อธิบายใน การเรียงลำดับและการกรองข้อมูล
แต่ถึงแม้จะไม่เพียงพอ ตัวอย่างเช่น พิจารณาความสัมพันธ์แบบสองทางระหว่างผู้ใช้และกลุ่ม ผู้ใช้สามารถเป็นสมาชิกของกลุ่ม และกลุ่มประกอบด้วยรายชื่อผู้ใช้ เมื่อถึงเวลาต้องตัดสินใจว่าผู้ใช้อยู่ในกลุ่มใด สิ่งต่างๆ จะซับซ้อนขึ้น
สิ่งที่จำเป็นคือวิธีที่สวยงามในการแสดงรายการกลุ่มที่ผู้ใช้เป็นสมาชิกและดึงเฉพาะข้อมูลสำหรับกลุ่มเหล่านั้น ดัชนี ของกลุ่มสามารถช่วยได้มากที่นี่:
// 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 ออกจากกลุ่ม จะต้องมีการอัปเดตในสองแห่ง
นี่เป็นความซ้ำซ้อนที่จำเป็นสำหรับความสัมพันธ์แบบสองทาง ช่วยให้คุณดึงข้อมูลสมาชิกของ Ada ได้อย่างรวดเร็วและมีประสิทธิภาพ แม้ว่ารายชื่อผู้ใช้หรือกลุ่มจะขยายเป็นล้าน หรือเมื่อกฎความปลอดภัยของฐานข้อมูลเรียลไทม์ป้องกันการเข้าถึงบางระเบียน
วิธีนี้จะแปลงข้อมูลโดยแสดงรายการ ID เป็นคีย์และตั้งค่าเป็น true ทำให้การตรวจสอบคีย์ทำได้ง่ายเหมือนกับการอ่าน /users/$uid/groups/$group_id
และตรวจสอบว่าเป็น null
หรือไม่ ดัชนีนั้นเร็วกว่าและมีประสิทธิภาพมากกว่าการสืบค้นหรือสแกนข้อมูล