הרחבת Realtime Database באמצעות Cloud Functions


עם Cloud Functions, אתם יכולים לטפל באירועים ב-Firebase Realtime Database בלי לעדכן את קוד הלקוח. ‫Cloud Functions מאפשר להריץ פעולות של Realtime Database עם הרשאות אדמיניסטרטיביות מלאות, ומוודא שכל שינוי ב-Realtime Database יעבור עיבוד בנפרד. אפשר לבצע שינויים באמצעות Firebase Realtime Databaseתמונת מצב של הנתונים או באמצעות Admin SDK.

במחזור חיים טיפוסי, פונקציית Firebase Realtime Database מבצעת את הפעולות הבאות:

  1. המתנה לשינויים בנתיב Realtime Database מסוים.
  2. מופעל כשמתרחש אירוע ומבצע את המשימות שלו.
  3. מקבל אובייקט נתונים שמכיל תמונת מצב של הנתונים שמאוחסנים בנתיב הזה.

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

הגדרת המיקום של הפונקציה

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

טיפול באירועי Realtime Database

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

אלה הגורמים המטפלים הזמינים בתגובה לאירועים של Realtime Database:

  • onValueWritten() מופעל כשנתונים נוצרים, מעודכנים או נמחקים ב-Realtime Database.
  • onValueCreated() מופעל רק כשנוצרים נתונים ב-Realtime Database.
  • onValueUpdated() מופעל רק כשנתונים מתעדכנים ב-Realtime Database.
  • onValueDeleted() מופעל רק כשנתונים נמחקים ב-Realtime Database.
  • on_value_written() מופעל כשנתונים נוצרים, מעודכנים או נמחקים ב-Realtime Database.
  • on_value_created() מופעל רק כשנוצרים נתונים ב-Realtime Database.
  • on_value_updated() מופעל רק כשנתונים מתעדכנים ב-Realtime Database.
  • on_value_deleted() מופעל רק כשנתונים נמחקים ב-Realtime Database.

ייבוא מודולים נדרשים

בקוד המקור של הפונקציה, צריך לייבא את מודולי ה-SDK שרוצים להשתמש בהם. בדוגמה הזו, צריך לייבא את המודולים HTTP ו-Realtime Database יחד עם המודול Firebase Admin SDK כדי לכתוב ל-Realtime Database.

// The Cloud Functions for Firebase SDK to setup triggers and logging.
const {onRequest} = require("firebase-functions/v2/https");
const {onValueCreated} = require("firebase-functions/v2/database");
const {logger} = require("firebase-functions");

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require("firebase-admin");
admin.initializeApp();
# The Cloud Functions for Firebase SDK to create Cloud Functions and set up triggers.
from firebase_functions import db_fn, https_fn

# The Firebase Admin SDK to access the Firebase Realtime Database.
from firebase_admin import initialize_app, db

app = initialize_app()

ציון המופע והנתיב

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

לדוגמה:

// All Realtime Database instances in default function region us-central1 at path "/user/{uid}"
// There must be at least one Realtime Database present in us-central1.
const onWrittenFunctionDefault = onValueWritten("/user/{uid}", (event) => {
  // …
});

// Instance named "my-app-db-2", at path "/user/{uid}".
// The "my-app-db-2" instance must exist in this region.
const OnWrittenFunctionInstance = onValueWritten(
  {
    ref: "/user/{uid}",
    instance: "my-app-db-2"
    // This example assumes us-central1, but to set location:
    // region: "europe-west1"
  },
  (event) => {
    // …
  }
);

// Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com.
// There must be at least one Realtime Database with "my-app-db-*" prefix in this region.
const onWrittenFunctionInstance = onValueWritten(
  {
    ref: "/user/{uid=*@gmail.com}",
    instance: "my-app-db-*"
    // This example assumes us-central1, but to set location:
    // region: "europe-west1"
  },
  (event) => {
    // …
  }
);
# All Realtime Database instances in default function region us-central1 at path "/user/{uid}"
# There must be at least one Realtime Database present in us-central1.
@db_fn.on_value_written(r"/user/{uid}")
def onwrittenfunctiondefault(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

# Instance named "my-app-db-2", at path "/user/{uid}".
# The "my-app-db-2" instance must exist in this region.
@db_fn.on_value_written(
    reference=r"/user/{uid}",
    instance="my-app-db-2",
    # This example assumes us-central1, but to set location:
    # region="europe-west1",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

# Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com.
# There must be at least one Realtime Database with "my-app-db-*" prefix in this region.
@db_fn.on_value_written(
    reference=r"/user/{uid=*@gmail.com}",
    instance="my-app-db-*",
    # This example assumes us-central1, but to set location:
    # region="europe-west1",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

הפרמטרים האלה מכוונים את הפונקציה לטפל בפעולות כתיבה בנתיב מסוים במופע Realtime Database.

הגדרות הנתיב תואמות לכל פעולות הכתיבה שקשורות לנתיב, כולל פעולות כתיבה שמתבצעות בכל מקום מתחתיו. אם מגדירים את הנתיב של הפונקציה כ-/foo/bar, היא תתאים לאירועים בשני המיקומים הבאים:

 /foo/bar
 /foo/bar/baz/really/deep/path

בכל מקרה, מערכת Firebase מפרשת שהאירוע מתרחש בשעה /foo/bar, ונתוני האירוע כוללים את הנתונים הישנים והחדשים בשעה /foo/bar. אם נתוני האירועים עשויים להיות גדולים, כדאי להשתמש בכמה פונקציות בנתיבים עמוקים יותר במקום בפונקציה אחת ליד שורש מסד הנתונים. כדי להשיג את הביצועים הטובים ביותר, מומלץ לבקש נתונים רק ברמה העמוקה ביותר שאפשר.

שימוש בתווים כלליים לחיפוש ובלכידה

אפשר להשתמש ב-{key}, {key=*}, {key=prefix*}, {key=*suffix} כדי לצלם. ‫*, prefix*, *suffix לשימוש בתו כללי בפלח יחיד. הערה: ** מייצג שימוש בתו כללי לחיפוש בכמה פלחים, ש-Realtime Database לא תומך בו. הסבר על דפוסי נתיבים

שימוש בתווים כלליים בנתיב. אפשר לציין רכיב נתיב כתו כללי:

  • שימוש בכוכבית, *. לדוגמה, foo/* תואם לכל צאצא ברמה אחת של היררכיית הצמתים מתחת ל-foo/.
  • שימוש בפלח שמכיל כוכבית בלבד, *. לדוגמה, foo/app*-us תואם לכל פלחי צאצא מתחת ל-foo/ עם הקידומת app והסיומת -us.

נתיבים עם תווים כלליים לחיפוש יכולים להתאים לכמה אירועים, למשל מפעולת כתיבה אחת. הוספה של

{
  "foo": {
    "hello": "world",
    "firebase": "functions"
  }
}

תואם לנתיב "/foo/*" פעמיים: פעם אחת עם "hello": "world" ופעם נוספת עם "firebase": "functions".

תיעוד נתיבים. אפשר ללכוד התאמות של נתיבים למשתנים עם שמות כדי להשתמש בהם בקוד הפונקציה (למשל, /user/{uid},‏ /user/{uid=*-us}).

הערכים של משתני הלכידה זמינים באובייקט database.DatabaseEvent.params של הפונקציה.

שימוש בתווים כלליים במופע. אפשר גם לציין רכיב של מופע באמצעות תו כללי לחיפוש. תו כללי של מופע יכול לכלול תחילית, סיומת או את שניהם (לדוגמה, my-app-*-prod).

תו כללי לחיפוש והפניה ללכידה

ב-Cloud Functions (דור שני) וב-Realtime Database, אפשר להשתמש בתבנית כשמציינים את ref ואת instance. בכל ממשק של טריגר יוצגו האפשרויות הבאות להגדרת היקף הפונקציה:

קביעת ref קביעת instance התנהגות
סינגל (/foo/bar) לא מציינים מטפל בהיקפים בכל המקרים באזור הפונקציה.
סינגל (/foo/bar) סינגל (‘my-new-db') מטפל בהיקפים למופע הספציפי באזור הפונקציה.
סינגל (/foo/bar) דוגמת עיצוב (‘inst-prefix*') הפונקציה מטפלת בהיקפים בכל המקרים שתואמים לתבנית באזור הפונקציה.
דוגמת עיצוב (/foo/{bar}) לא מציינים מטפל בהיקפים בכל המקרים באזור הפונקציה.
דוגמת עיצוב (/foo/{bar}) סינגל (‘my-new-db') מטפל בהיקפים למופע הספציפי באזור הפונקציה.
דוגמת עיצוב (/foo/{bar}) דוגמת עיצוב (‘inst-prefix*') הפונקציה מטפלת בהיקפים בכל המקרים שתואמים לתבנית באזור הפונקציה.

טיפול בנתוני אירועים

כשמופעל אירוע Realtime Database, מועבר אובייקט Event לפונקציית הטיפול. לאובייקט הזה יש מאפיין data, שבאירועי יצירה ומחיקה מכיל תמונת מצב של הנתונים שנוצרו או נמחקו.

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

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
// for all databases in 'us-central1'
exports.makeuppercase = onValueCreated(
    "/messages/{pushId}/original",
    (event) => {
    // Grab the current value of what was written to the Realtime Database.
      const original = event.data.val();
      logger.log("Uppercasing", event.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing
      // asynchronous tasks inside a function, such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the
      // Realtime Database returns a Promise.
      return event.data.ref.parent.child("uppercase").set(uppercase);
    },
);
@db_fn.on_value_created(reference="/messages/{pushId}/original")
def makeuppercase(event: db_fn.Event[Any]) -> None:
    """Listens for new messages added to /messages/{pushId}/original and
    creates an uppercase version of the message to /messages/{pushId}/uppercase
    """

    # Grab the value that was written to the Realtime Database.
    original = event.data
    if not isinstance(original, str):
        print(f"Not a string: {event.reference}")
        return

    # Use the Admin SDK to set an "uppercase" sibling.
    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent = db.reference(event.reference).parent
    if parent is None:
        print("Message can't be root node.")
        return
    parent.child("uppercase").set(upper)

קריאת הערך הקודם

באירועים write או update, המאפיין data הוא אובייקט Change שמכיל שתי תמונות מצב שמייצגות את מצב הנתונים לפני ואחרי אירוע ההפעלה. לאובייקט Change יש מאפיין before שמאפשר לבדוק מה נשמר ב-Realtime Database לפני האירוע, ומאפיין after שמייצג את מצב הנתונים אחרי שהאירוע התרחש.

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

  exports makeUppercase = onValueWritten("/messages/{pushId}/original", (event) => {
        // Only edit data when it is first created.
        if (event.data.before.exists()) {
          return null;
        }
        // Exit when the data is deleted.
        if (!event.data.after.exists()) {
          return null;
        }
        // Grab the current value of what was written to the Realtime Database.
        const original = event.data.after.val();
        console.log('Uppercasing', event.params.pushId, original);
        const uppercase = original.toUpperCase();
        // You must return a Promise when performing asynchronous tasks inside a Functions such as
        // writing to the Firebase Realtime Database.
        // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
        return event.data.after.ref.parent.child('uppercase').set(uppercase);
      });
@db_fn.on_value_written(reference="/messages/{pushId}/original")
def makeuppercase2(event: db_fn.Event[db_fn.Change]) -> None:
    """Listens for new messages added to /messages/{pushId}/original and
    creates an uppercase version of the message to /messages/{pushId}/uppercase
    """

    # Only edit data when it is first created.
    if event.data.before is not None:
        return

    # Exit when the data is deleted.
    if event.data.after is None:
        return

    # Grab the value that was written to the Realtime Database.
    original = event.data.after
    if not hasattr(original, "upper"):
        print(f"Not a string: {event.reference}")
        return

    # Use the Admin SDK to set an "uppercase" sibling.
    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent = db.reference(event.reference).parent
    if parent is None:
        print("Message can't be root node.")
        return
    parent.child("uppercase").set(upper)