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


עם 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 DatabaseCloud 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);
      }
    });

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

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);
    });