מדריך זה עוסק בכמה מהמושגים המרכזיים בארכיטקטורת הנתונים, שיעזרו לכם לבנות את נתוני ה-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": { ... }
}
}
עכשיו אפשר לחזור על רשימת החדרים על ידי הורדה של מעט בייטים בכל שיחה, אחזור מהיר של מטא-נתונים לצורך רישום או הצגה חדרים בממשק משתמש. ניתן לאחזר הודעות בנפרד ולהציג אותן כאשר הן מגיעות, באופן שמאפשר לממשק המשתמש לשמור על יכולת תגובה ומהירות.
יצירת נתונים שניתן להתאים לעומס
כשיוצרים אפליקציות, בדרך כלל עדיף להוריד קבוצת משנה של רשימה. המצב הזה נפוץ במיוחד אם הרשימה מכילה אלפי רשומות. כשהקשר הזה סטטי ופונה בכיוון אחד, אפשר פשוט להטמיע את אובייקטי הצאצא מתחת לאובייקט ההורה.
לפעמים הקשר הזה דינמי יותר, או שצריך לבטל את הנורמליזציה של הנתונים האלה. פעמים רבות אפשר לבטל את הנורמליזציה של הנתונים באמצעות שאילתה לאחזור קבוצת משנה של הנתונים, כמו שמתואר מיון וסינון של נתונים
אבל גם זה לא מספיק. לדוגמה, נבחן מערכת יחסים דו-כיוונית, בין משתמשים לקבוצות. משתמשים יכולים להשתייך לקבוצה, וקבוצות מורכבות מרשימת משתמשים. כשמגיע הזמן להחליט לאילו קבוצות משתמש מסוים שייך, העניינים מסתבכים.
נדרשת שיטה אלגנטית כדי להציג ברשימה את הקבוצות שאליהן המשתמש שייך יאחזרו רק נתונים עבור הקבוצות האלה. אינדקס של קבוצות יכול לעזור במידה רבה מאוד:
// 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
}
},
...
}
}
ייתכן שתבחינו שהפעולה הזו מעתיקה חלק מהנתונים על ידי שמירת הקשר
גם לרשומה של עדה וגם לקבוצה. עכשיו הדף alovelace
נוסף לאינדקס כקבוצה, והדף techpioneers
מופיע בפרופיל של Ada. לכן, כדי למחוק את Ada מהקבוצה, צריך לעדכן אותה בשני מקומות.
זו יתירות הכרחית של יחסים דו-כיווניים. הוא מאפשר לכם: לאחזר במהירות וביעילות את החברויות של עדה, גם כאשר רשימת המשתמשים עד גודל של מיליונים או כאשר כללי האבטחה של מסדי נתונים בזמן אמת למנוע גישה לחלק מהרשומות.
הגישה הזו, שבה הנתונים הופכים על ידי ציון המזהים כמפתחות והגדרת הערך כ-true, מאפשרת לבדוק מפתח באותה קלות שבה קוראים את הערך /users/$uid/groups/$group_id
ובודקים אם הוא null
. האינדקס מהיר יותר ויעיל הרבה יותר מאשר שליחת שאילתות או סריקה של הנתונים.