טריגרים של מסד נתונים בזמן אמת


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

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

  1. הפונקציה ממתינה לשינויים במיקום Realtime Database מסוים.
  2. מופעל כשאירוע מתרחש ומבצע את המשימות שלו (מידע נוסף זמין בקטע מה אפשר לעשות? עם Cloud Functions? לדוגמה, תרחישים לדוגמה).
  3. מקבל אובייקט נתונים שמכיל תמונת מצב של הנתונים המאוחסנים במסמך שצוין.

הפעלת פונקציית Realtime Database

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

הגדרת הגורם המטפל באירועים

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

  • onWrite(), שמופעל כאשר נוצרים, מתעדכנים או נמחקים נתונים ב-Realtime Database.
  • onCreate(), שמופעל כשנוצרים נתונים חדשים ב-Realtime Database.
  • onUpdate(), שמופעל כשהנתונים מתעדכנים ב-Realtime Database .
  • onDelete(), שמופעל כאשר נתונים נמחקים מ-Realtime Database .

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

כדי לקבוע מתי ואיפה הפונקציה תופעל, צריך להפעיל את ref(path) כדי לציין נתיב, ואם רוצים, לציין מכונה של Realtime Database באמצעות instance('INSTANCE_NAME'). אם לא מציינים מכונה, הפונקציה תופעל במכונה Realtime Database שמוגדרת כברירת מחדל לפרויקט ב-Firebase. לדוגמה:

  • מופע ברירת המחדל של Realtime Database: functions.database.ref('/foo/bar')
  • מכונה בשם 'my-app-db-2': functions.database.instance('my-app-db-2').ref('/foo/bar')

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

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

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

אפשר לציין רכיב נתיב כתו כללי לחיפוש על ידי הוספת עיגול מסולסל סוגריים; ref('foo/{bar}') תואם לכל צאצא של /foo. הערכים של רכיבי הנתיב עם תווים כלליים זמינים באובייקט EventContext.params של הפונקציה. בדוגמה הזו, הערך זמין באופן הבא: context.params.bar

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

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

תואם לנתיב "/foo/{bar}" פעמיים: פעם אחת עם "hello": "world" ושוב עם "firebase": "functions".

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

בטיפול באירוע Realtime Database, אובייקט הנתונים המוחזר הוא DataSnapshot. עבור אירועים של onWrite או onUpdate, הפרמטר הראשון הוא אובייקט Change שמכיל שתי תמונות מצב שמייצגים את מצב הנתונים ואחרי האירוע המפעיל. עבור אירועים מסוג onCreate ו-onDelete, אובייקט הנתונים המוחזר הוא תמונת מצב של הנתונים שנוצרו או נמחקו.

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

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snapshot, context) => {
      // Grab the current value of what was written to the Realtime Database.
      const original = snapshot.val();
      functions.logger.log('Uppercasing', context.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 snapshot.ref.parent.child('uppercase').set(uppercase);
    });

גישה לפרטי אימות של משתמשים

מקור: EventContext.auth וגם EventContext.authType, שיש לך גישה פרטי המשתמש, כולל הרשאות, של המשתמש שהפעיל פונקציה. זה יכול להיות שימושי לצורך אכיפה של כללי אבטחה, שמאפשרות לפונקציה להשלים פעולות שונות בהתאם רמת הרשאות:

const functions = require('firebase-functions/v1');
const admin = require('firebase-admin');

exports.simpleDbFunction = functions.database.ref('/path')
    .onCreate((snap, context) => {
      if (context.authType === 'ADMIN') {
        // do something
      } else if (context.authType === 'USER') {
        console.log(snap.val(), 'written by', context.auth.uid);
      }
    });

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

exports.impersonateMakeUpperCase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snap, context) => {
      const appOptions = JSON.parse(process.env.FIREBASE_CONFIG);
      appOptions.databaseAuthVariableOverride = context.auth;
      const app = admin.initializeApp(appOptions, 'app');
      const uppercase = snap.val().toUpperCase();
      const ref = snap.ref.parent.child('uppercase');

      const deleteApp = () => app.delete().catch(() => null);

      return app.database().ref(ref).set(uppercase).then(res => {
        // Deleting the app is necessary for preventing concurrency leaks
        return deleteApp().then(() => res);
      }).catch(err => {
        return deleteApp().then(() => Promise.reject(err));
      });
    });

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

לאובייקט Change יש before שמאפשר לבדוק את מה שנשמר ב-Realtime Database לפני אירוע. המאפיין before מחזיר DataSnapshot שבו כל (לדוגמה, val() וגם exists()) להתייחס לערך הקודם. אפשר לקרוא את הערך החדש שוב באמצעות את DataSnapshot המקורי או לקרוא את after לנכס. הנכס הזה בכל Change הוא DataSnapshot אחר שמייצג מצב הנתונים אחרי האירוע.

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

exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onWrite((change, context) => {
      // Only edit data when it is first created.
      if (change.before.exists()) {
        return null;
      }
      // Exit when the data is deleted.
      if (!change.after.exists()) {
        return null;
      }
      // Grab the current value of what was written to the Realtime Database.
      const original = change.after.val();
      console.log('Uppercasing', context.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 change.after.ref.parent.child('uppercase').set(uppercase);
    });