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

באמצעות Cloud Functions, אפשר לפרוס את הקוד של Node.js כדי לטפל באירועים שהופעלו על ידי שינויים במסד הנתונים Cloud Firestore שלך. כך אפשר להוסיף בקלות בצד השרת פונקציונליות באפליקציה שלך בלי להפעיל שרתים משלך.

כדי לראות דוגמאות של תרחישים לדוגמה, ראו מה אפשר לעשות עם Cloud Functions? או Functions Samples (דוגמאות לפונקציות) במאגר של GitHub.

טריגרים של פונקציה אחת (Cloud Firestore)

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

סוג אירוע הפעלה
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 ...
    });

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

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

נתוני אירוע

נתוני קריאה

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