מבנה את מסד הנתונים שלך

מדריך זה מכסה כמה מהמושגים המרכזיים בארכיטקטורת נתונים ושיטות עבודה מומלצות לבניית נתוני JSON במסד הנתונים שלך בזמן אמת של Firebase.

בניית מסד נתונים מובנה כהלכה דורשת לא מעט מחשבה מוקדמת. והכי חשוב, אתה צריך לתכנן כיצד הנתונים יישמרו ואחזור מאוחר יותר כדי להפוך את התהליך הזה לקל ככל האפשר.

איך הנתונים בנויים: זה עץ JSON

כל הנתונים של Firebase Realtime Database מאוחסנים כאובייקטי 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": { ... }
  }
}

כעת ניתן לעבור על רשימת החדרים על ידי הורדת בתים בודדים בלבד לשיחה, אחזור מהיר של מטא נתונים לרישום או הצגת חדרים בממשק משתמש. ניתן להביא הודעות בנפרד ולהציג כשהן מגיעות, מה שמאפשר לממשק המשתמש להישאר מגיב ומהיר.

צור נתונים בקנה מידה

בעת בניית אפליקציות, לרוב עדיף להוריד קבוצת משנה של רשימה. זה נפוץ במיוחד אם הרשימה מכילה אלפי רשומות. כאשר מערכת היחסים הזו היא סטטית וחד-כיוונית, אתה יכול פשוט לקנן את חפצי הילד מתחת להורה.

לפעמים הקשר הזה הוא דינמי יותר, או שייתכן שיהיה צורך לבטל את הנורמליזציה של הנתונים האלה. פעמים רבות אתה יכול לבטל את הנורמליזציה של הנתונים על ידי שימוש בשאילתה כדי לאחזר קבוצת משנה של הנתונים, כפי שנדון ב- Retrieve Data .

אבל אפילו זה עשוי להיות לא מספיק. קחו למשל קשר דו-כיווני בין משתמשים וקבוצות. משתמשים יכולים להשתייך לקבוצה, וקבוצות מורכבות מרשימה של משתמשים. כשמגיע הזמן להחליט לאילו קבוצות משתמש שייך, הדברים מסתבכים.

מה שצריך הוא דרך אלגנטית לרשום את הקבוצות שהמשתמש משתייך אליהן ולאחזר רק נתונים עבור הקבוצות האלה. אינדקס של קבוצות יכול לעזור רבות כאן:

// 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 רשומים בפרופיל של עדה. אז כדי למחוק את עדה מהקבוצה, יש לעדכן אותה בשני מקומות.

זוהי יתירה הכרחית למערכות יחסים דו-כיווניות. זה מאפשר לך להשיג במהירות וביעילות את החברות של עדה, גם כאשר רשימת המשתמשים או הקבוצות מצטמצמת למיליונים או כאשר כללי אבטחה של מסד נתונים Realtime מונעים גישה לחלק מהרשומות.

גישה זו, הפיכת הנתונים על ידי רישום המזהים כמפתחות והגדרת הערך כ-true, הופכת את בדיקת המפתח לפשוטה כמו קריאת /users/$uid/groups/$group_id ובדיקה אם הוא null . האינדקס מהיר יותר והרבה יותר יעיל מאשר שאילתה או סריקת הנתונים.

הצעדים הבאים