שמירת נתונים באמצעות מסד נתונים בזמן אמת ב-Firebase עבור C++

מתחילים

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

קבלת DatabaseReference

כדי לכתוב נתונים במסד הנתונים, נדרשת מופע של DatabaseReference:

    // Get the root reference location of the database.
    firebase::database::DatabaseReference dbref = database->GetReference();

שמירת נתונים

יש ארבע שיטות לכתיבה של נתונים ב-Firebase Realtime Database:

שיטה שימושים נפוצים
SetValue() כתיבת או החלפת נתונים בנתיב מוגדר, כמו users/<user-id>/<username>.
PushChild() הוספה לרשימת נתונים. בכל פעם שמפעילים את Push(), מערכת Firebase יוצרת מפתח ייחודי שאפשר להשתמש בו גם כמזהה ייחודי, למשל user-scores/<user-id>/<unique-score-id>.
UpdateChildren() לעדכן חלק מהמפתחות לנתיב מוגדר בלי להחליף את כל הנתונים.
RunTransaction() עדכון נתונים מורכבים שעשויים להיפגם בגלל עדכונים בו-זמניים.

כתיבת, עדכון או מחיקה של נתונים בהפניה

פעולות כתיבה בסיסיות

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

  • Null (הנתונים נמחקים)
  • מספרים שלמים (64 ביט)
  • מספרי נקודה צפה (floating-point) עם דיוק כפול
  • ערכים בוליאניים
  • מחרוזות
  • וקטורים של וריאנטים
  • מפות של מחרוזות לוריאציות

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

dbref.Child("users").Child(userId).Child("username").SetValue(name);

הוספה לרשימה של נתונים

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

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

עדכון שדות ספציפיים

כדי לכתוב בו-זמנית לצאצאים ספציפיים של צומת בלי להחליף צומתי צאצא אחרים, משתמשים ב-method UpdateChildren().

כשקוראים ל-UpdateChildren(), אפשר לעדכן ערכים של צאצאים ברמה נמוכה יותר על ידי ציון נתיב למפתח. אם הנתונים מאוחסנים בכמה מיקומים כדי להתאים אותם טוב יותר לעומס, אפשר לעדכן את כל המופעים של הנתונים האלה באמצעות fan-out. לדוגמה, למשחק יכולה להיות מחלקה LeaderboardEntry כזו:

class LeaderboardEntry {
  std::string uid;
  int score = 0;

 public:
  LeaderboardEntry() {
  }

  LeaderboardEntry(std::string uid, int score) {
    this->uid = uid;
    this->score = score;
  }

  std::map&ltstd::string, Object&gt ToMap() {
    std::map&ltstring, Variant&gt result = new std::map&ltstring, Variant&gt();
    result["uid"] = Variant(uid);
    result["score"] = Variant(score);

    return result;
  }
}

כדי ליצור LeaderboardEntry ולעדכן אותו בו-זמנית לפי הפיד של התוצאות האחרונות ולפי רשימת התוצאות של המשתמש, המשחק משתמש בקוד הבא:

void WriteNewScore(std::string userId, int score) {
  // Create new entry at /user-scores/$userid/$scoreid and at
  // /leaderboard/$scoreid simultaneously
  std::string key = dbref.Child("scores").PushChild().GetKey();
  LeaderBoardEntry entry = new LeaderBoardEntry(userId, score);
  std::map&ltstd::string, Variant&gt entryValues = entry.ToMap();

  std::map&ltstring, Variant&gt childUpdates = new std::map&ltstring, Variant&gt();
  childUpdates["/scores/" + key] = entryValues;
  childUpdates["/user-scores/" + userId + "/" + key] = entryValues;

  dbref.UpdateChildren(childUpdates);
}

בדוגמה הזו משתמשים ב-PushChild() כדי ליצור רשומה בצומת שמכיל את הרשומות של כל המשתמשים ב-/scores/$key, ולאחזר את המפתח בו-זמנית באמצעות key(). לאחר מכן אפשר להשתמש במפתח כדי ליצור רשומה שנייה בציוני הדרך של המשתמש ב-/user-scores/$userid/$key.

בעזרת הנתיבים האלה אפשר לבצע עדכונים בו-זמנית במספר מיקומים בעץ ה-JSON באמצעות קריאה אחת ל-UpdateChildren(). לדוגמה, בקוד הבא נוצרת הרשומה החדשה בשני המיקומים. עדכונים בו-זמניים שמתבצעים באופן הזה הם אטומיים: כל העדכונים מתבצעים בהצלחה או שהם כולם נכשלים.

מחיקת נתונים

הדרך הפשוטה ביותר למחוק נתונים היא להפעיל את RemoveValue() על הפניה למיקום של הנתונים האלה.

אפשר גם למחוק על ידי ציון null Variant בתור הערך של פעולת כתיבה אחרת, כמו SetValue() או UpdateChildren(). אפשר להשתמש בשיטה הזו עם UpdateChildren() כדי למחוק כמה צאצאים בקריאה אחת ל-API.

לדעת מתי הנתונים שלכם מחויבים.

כדי לדעת מתי הנתונים שלכם הועברו לשרת Firebase Realtime Database, בודקים את התוצאה Future.

שמירת נתונים כעסקאות

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

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

void AddScoreToLeaders(std::string email,
                       long score,
                       DatabaseReference leaderBoardRef) {
  leaderBoardRef.RunTransaction([](firebase::database::MutableData* mutableData) {
    if (mutableData.children_count() &gt= MaxScores) {
      long minScore = LONG_MAX;
      MutableData *minVal = null;
      std::vector&ltMutableData&gt children = mutableData.children();
      std::vector&ltMutableData&gt::iterator it;
      for (it = children.begin(); it != children.end(); ++it) {
        if (!it->value().is_map())
          continue;
        long childScore = (long)it->Child("score").value().int64_value();
        if (childScore &lt minScore) {
          minScore = childScore;
          minVal = &amp*it;
        }
      }
      if (minScore &gt score) {
        // The new score is lower than the existing 5 scores, abort.
        return kTransactionResultAbort;
      }

      // Remove the lowest score.
      children.Remove(minVal);
    }

    // Add the new high score.
    std::map&ltstd::string, Variant&gt newScoreMap =
      new std::map&ltstd::string, Variant&gt();
    newScoreMap["score"] = score;
    newScoreMap["email"] = email;
    children.Add(newScoreMap);
    mutableData->set_value(children);
    return kTransactionResultSuccess;
  });
}

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

כתיבת נתונים במצב אופליין

אם לקוח יתנתק מהרשת, האפליקציה תמשיך לפעול בצורה תקינה.

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

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

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

השלבים הבאים