טריגרים של Cloud Firestore


בעזרת Cloud Functions אפשר לטפל באירועים ב-Cloud Firestore בלי לעדכן את קוד הלקוח. אפשר לבצע שינויים ב-Cloud Firestore דרך הממשק של תמונת המצב של המסמך או דרך Admin SDK.

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

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

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

טריגרים של פונקציות Cloud Firestore

Cloud Functions for Firebase SDK מייצא אובייקט functions.firestore שמאפשר ליצור רכיבי handler שקשורים לאירועים ספציפיים ב-Cloud Firestore.

סוג אירוע Trigger
onCreate מופעל כשמסמך נכתב בפעם הראשונה.
onUpdate מופעל כשמסמך כבר קיים והערך שלו השתנה.
onDelete מופעל כשמוחקים מסמך עם נתונים.
onWrite מופעל כאשר onCreate, onUpdate או onDelete מופעלים.

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

כתיבת פונקציות שהופעלו על ידי Cloud Firestore

הגדרת טריגר לפונקציה

כדי להגדיר טריגר של Cloud Firestore, צריך לציין נתיב של מסמך ואת סוג האירוע:

Node.js

const functions = require('firebase-functions');

exports.myFunction = functions.firestore
  .document('my-collection/{docId}')
  .onWrite((change, context) => { /* ... */ });

נתיבי המסמכים יכולים להפנות למסמך ספציפי או לדפוס של תווים כלליים לחיפוש.

ציון מסמך יחיד

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

Node.js

// Listen for any change on document `marie` in collection `users`
exports.myFunctionName = functions.firestore
    .document('users/marie').onWrite((change, context) => {
      // ... Your code here
    });

ציון קבוצת מסמכים באמצעות תווים כלליים

אם רוצים לצרף טריגר לקבוצה של מסמכים, למשל מסמך כלשהו באוסף מסוים, צריך להשתמש ב-{wildcard} במקום במזהה המסמך:

Node.js

// Listen for changes in all documents in the 'users' collection
exports.useWildcard = functions.firestore
    .document('users/{userId}')
    .onWrite((change, context) => {
      // If we set `/users/marie` to {name: "Marie"} then
      // context.params.userId == "marie"
      // ... and ...
      // change.after.data() == {name: "Marie"}
    });

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

אם מסמך ב-users יש אוספי משנה, ושדה באחד מאוספי המשנה האלה משתנה, התו הכללי לחיפוש userId לא יופעל.

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

Node.js

// Listen for changes in all documents in the 'users' collection and all subcollections
exports.useMultipleWildcards = functions.firestore
    .document('users/{userId}/{messageCollectionId}/{messageId}')
    .onWrite((change, context) => {
      // If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
      // context.params.userId == "marie";
      // context.params.messageCollectionId == "incoming_messages";
      // context.params.messageId == "134";
      // ... and ...
      // change.after.data() == {body: "Hello"}
    });

טריגרים של אירועים

הפעלת פונקציה כשיוצרים מסמך חדש

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

Node.js

exports.createUser = functions.firestore
    .document('users/{userId}')
    .onCreate((snap, context) => {
      // Get an object representing the document
      // e.g. {'name': 'Marie', 'age': 66}
      const newValue = snap.data();

      // access a particular field as you would any JS property
      const name = newValue.name;

      // perform desired operations ...
    });

הפעלת פונקציה במהלך עדכון של מסמך

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

Node.js

exports.updateUser = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
      // Get an object representing the document
      // e.g. {'name': 'Marie', 'age': 66}
      const newValue = change.after.data();

      // ...or the previous value before this update
      const previousValue = change.before.data();

      // access a particular field as you would any JS property
      const name = newValue.name;

      // perform desired operations ...
    });

הפעלת פונקציה כשמוחקים מסמך

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

Node.js

exports.deleteUser = functions.firestore
    .document('users/{userID}')
    .onDelete((snap, context) => {
      // Get an object representing the document prior to deletion
      // e.g. {'name': 'Marie', 'age': 66}
      const deletedValue = snap.data();

      // perform desired operations ...
    });

הפעלת פונקציה לכל השינויים במסמך

אם סוג האירוע שמופעל לא חשוב לכם, אפשר להאזין לכל השינויים במסמך Cloud Firestore באמצעות הפונקציה onWrite() עם תו כללי לחיפוש. הפונקציה לדוגמה הזו מפעילה modifyUser אם משתמש נוצר, מעודכן או נמחק:

Node.js

exports.modifyUser = functions.firestore
    .document('users/{userID}')
    .onWrite((change, context) => {
      // Get an object with the current document value.
      // If the document does not exist, it has been deleted.
      const document = change.after.exists ? change.after.data() : null;

      // Get an object with the previous document value (for update or delete)
      const oldDocument = change.before.data();

      // perform desired operations ...
    });

קריאה וכתיבה של נתונים

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

נתוני אירוע

קריאת נתונים

כשפונקציה מופעלת, יכול להיות שתרצו לקבל נתונים ממסמך שעודכן, או לקבל את הנתונים לפני העדכון. אפשר לקבל את הנתונים הקודמים באמצעות change.before.data(), שמכיל את תמונת המצב של המסמך לפני העדכון. באופן דומה, change.after.data() מכיל את המצב של תמונת המצב של המסמך אחרי העדכון.

Node.js

exports.updateUser2 = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
      // Get an object representing the current document
      const newValue = change.after.data();

      // ...or the previous value before this update
      const previousValue = change.before.data();
    });

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

Node.js

// Fetch data using standard accessors
const age = snap.data().age;
const name = snap.data()['name'];

// Fetch data using built in accessor
const experience = snap.get('experience');

כתיבת נתונים

כל הפעלה של פונקציה משויכת למסמך ספציפי במסד הנתונים של Cloud Firestore. אפשר לגשת למסמך בתור DocumentReference במאפיין ref של קובץ ה-snapshot שמוחזר לפונקציה.

ה-DocumentReference הזה מגיע מ- Cloud Firestore Node.js SDK וכולל שיטות כמו update(), set() ו-remove(), כך שתוכלו לשנות בקלות את המסמך שהפעיל את הפונקציה.

Node.js

// Listen for updates to any `user` document.
exports.countNameChanges = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
      // Retrieve the current and previous value
      const data = change.after.data();
      const previousData = change.before.data();

      // We'll only update if the name has changed.
      // This is crucial to prevent infinite loops.
      if (data.name == previousData.name) {
        return null;
      }

      // Retrieve the current count of name changes
      let count = data.name_change_count;
      if (!count) {
        count = 0;
      }

      // Then return a promise of a set operation to update the count
      return change.after.ref.set({
        name_change_count: count + 1
      }, {merge: true});
    });

נתונים מחוץ לאירוע הטריגר

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

Node.js

const admin = require('firebase-admin');
admin.initializeApp();

const db = admin.firestore();

exports.writeToFirestore = functions.firestore
  .document('some/doc')
  .onWrite((change, context) => {
    db.doc('some/otherdoc').set({ ... });
  });

הגבלות

שימו לב למגבלות הבאות לגבי טריגרים של Cloud Firestore עבור Cloud Functions:

  • ההזמנה לא מובטחת. שינויים מהירים עלולים לגרום להפעלה של פונקציות בסדר בלתי צפוי.
  • אירועים נשלחים לפחות פעם אחת, אבל אירוע אחד יכול לגרום למספר הפעלות של פונקציה. כדאי להימנע בהתאם למכונות חד-פעמיות, ולכתוב פונקציות אידמפוטנטיות.
  • כדי להשתמש ב-Cloud Firestore במצב Datastore יש צורך ב-Cloud Functions (דור שני). ב-Cloud Functions (דור ראשון) אין תמיכה במצב Datastore.
  • Cloud Functions (דור ראשון) פועל רק עם מסד הנתונים "(ברירת מחדל)" ולא תומך במסדי נתונים בעלי שם של Cloud Firestore. כדי להגדיר אירועים למסדי נתונים בעלי שם, צריך להשתמש ב-Cloud Functions (דור שני).
  • טריגר משויך למסד נתונים יחיד. לא ניתן ליצור גורם מפעיל שתואם למספר מסדי נתונים.
  • מחיקה של מסד נתונים לא מוחקת באופן אוטומטי את הטריגרים של אותו מסד נתונים. הטריגר מפסיק להעביר אירועים אבל ממשיך להתקיים עד שמוחקים את הטריגר.
  • אם אירוע תואם חורג מגודל הבקשה המקסימלי, ייתכן שהאירוע לא יועבר ל-Cloud Functions (דור ראשון).
    • אירועים שלא נמסרו בגלל גודל הבקשה נרשמים ביומני הפלטפורמה ונכללים בחישוב השימוש ביומן בפרויקט.
    • היומנים האלה מופיעים ב-Logs Explorer עם ההודעה "Event לא יכול לספק לפונקציה Cloud Functions בגלל שהגודל חורג מהמגבלה של דור ראשון..." בחומרה של error. השם של הפונקציה מופיע מתחת לשדה functionName. אם השדה receiveTimestamp עדיין נמצא בטווח של שעה מעכשיו, אפשר להסיק את תוכן האירוע בפועל על ידי קריאת המסמך הרלוונטי עם תמונת מצב לפני ואחרי חותמת הזמן.
    • כדי להימנע מקצב כזה, אפשר:
      • העברה ושדרוג ל-Cloud Functions (דור שני)
      • הקטנת המסמך
      • מחיקת הפונקציות הנדונות של Cloud Functions
    • תוכלו להשבית את הרישום ביומן עצמו באמצעות החרגות, אבל שימו לב שהאירועים הבעייתיים עדיין לא יועברו.