הרחבת Firestore באמצעות Cloud Functions
בעזרת Cloud Functions, אתם יכולים לפרוס קוד Node.js כדי לטפל באירועים שמופעלים על ידי שינויים במסד הנתונים של Firestore. כך תוכלו להוסיף בקלות פונקציונליות בצד השרת לאפליקציה שלכם בלי להפעיל שרתים משלכם.
דוגמאות לתרחישי שימוש זמינות במאמר מה אפשר לעשות עם Cloud Functions? או במאגר Functions Samples ב-GitHub.
דרישות לגבי מהדורה ומצב
המסמך הזה רלוונטי למהדורת Firestore Standard במצב Native.
מהדורת Firestore Enterprise לא תומכת ב-Cloud Functions (דור ראשון). במקום זאת, כדאי להשתמש ב-Cloud Run functions (דור שני).
טריגרים של פונקציות ב-Firestore
Cloud Functions for Firebase SDK מייצא אובייקט functions.firestore שמאפשר ליצור פונקציות handler שקשורות לאירועים ספציפיים ב-Firestore.
| סוג האירוע | הטריגר |
|---|---|
onCreate |
מופעל כשמסמך נכתב בפעם הראשונה. |
onUpdate |
מופעל כשמסמך כבר קיים וערך כלשהו בו משתנה. |
onDelete |
מופעל כשמסמך עם נתונים נמחק. |
onWrite |
מופעל כשמופעלים onCreate, onUpdate או onDelete. |
אם עדיין לא הפעלתם פרויקט ב-Cloud Functions for Firebase, כדאי לקרוא את המאמר תחילת העבודה: כתיבה ופריסה של הפונקציות הראשונות כדי להגדיר את הפרויקט שלכם ב-Cloud Functions for Firebase.
כתיבת פונקציות שמופעלות על ידי Firestore
הגדרת טריגר של פונקציה
כדי להגדיר טריגר של 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"} });
טריגרים לאירועים
הפעלת פונקציה כשנוצר מסמך חדש
אפשר להפעיל פונקציה בכל פעם שנוצר מסמך חדש באוסף באמצעות onCreate() handler עם wildcard.
הפונקציה בדוגמה הזו קוראת ל-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 ... });
הפעלת פונקציה לכל השינויים במסמך
אם לא חשוב לכם סוג האירוע שמופעל, אתם יכולים להאזין לכל השינויים במסמך 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');
כתיבת נתונים
כל הפעלה של פונקציה משויכת למסמך ספציפי במסד הנתונים שלכם ב-Firestore. אפשר לגשת למסמך הזה כ-DocumentReference במאפיין ref של התמונה שמוחזרת לפונקציה.
האובייקט DocumentReference מגיע מ-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 פועלות בסביבה מהימנה, כלומר הן מורשות כחשבון שירות בפרויקט שלכם. אתם יכולים לבצע פעולות קריאה וכתיבה באמצעות SDK של Firebase לאדמינים:
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({ ... });
});
מגבלות
שימו לב למגבלות הבאות לגבי טריגרים של Firestore לפונקציות Cloud Run:
- פונקציות Cloud Run (דור ראשון) דורשות מסד נתונים קיים מסוג '(default)' במצב Firestore Native. הוא לא תומך במסדי נתונים בעלי שם ב-Firestore או במצב Datastore. במקרים כאלה, צריך להשתמש בפונקציות Cloud Run (דור שני) כדי להגדיר אירועים.
- הגדרה של פרויקטים שונים באמצעות פונקציות Cloud Run וטריגר של Firestore היא מגבלה. כדי להגדיר פונקציות Cloud Run להפעלה על ידי Firestore, הן צריכות להיות באותו פרויקט.
- אנחנו לא מתחייבים לגבי סדר ההזמנה. שינויים מהירים יכולים להפעיל קריאות לפונקציות בסדר לא צפוי.
- האירועים מועברים לפחות פעם אחת, אבל אירוע יחיד עשוי להוביל להפעלות מרובות של פונקציות. אל תסתמכו על מנגנונים של 'פעם אחת בלבד', ותכתבו פונקציות אידמפוטנטיות.
- Firestore במצב Datastore דורש פונקציות Cloud Run (דור שני). פונקציות Cloud Run (דור ראשון) לא תומכות במצב Datastore.
- טריגר משויך למסד נתונים יחיד. אי אפשר ליצור טריגר שתואם לכמה מסדי נתונים.
- מחיקה של מסד נתונים לא תגרום למחיקה אוטומטית של טריגרים במסד הנתונים הזה. הטריגר מפסיק להעביר אירועים אבל ממשיך להתקיים עד שמוחקים את הטריגר.
- אם אירוע תואם חורג מהגודל המקסימלי של הבקשה, יכול להיות שהאירוע לא יועבר לפונקציות Cloud Run (דור ראשון).
- אירועים שלא נמסרו בגלל גודל הבקשה מתועדים ביומני הפלטפורמה ונספרים בשימוש ביומן של הפרויקט.
- אפשר למצוא את היומנים האלה בכלי Logs Explorer עם ההודעה 'לא ניתן למסור את האירוע לפונקציית Cloud בגלל שהגודל חורג מהמגבלה של דור ראשון...' ברמת חומרה
error. שם הפונקציה מופיע בשדהfunctionName. אם השדהreceiveTimestampעדיין נמצא בטווח של שעה מהזמן הנוכחי, אפשר להסיק את תוכן האירוע בפועל על ידי קריאת המסמך המדובר עם תמונת מצב לפני ואחרי חותמת הזמן. - כדי להימנע מקצב צעדים כזה, אתם יכולים:
- העברה ושדרוג לפונקציות Cloud Run (דור שני)
- הקטנת המסמך
- מחיקת פונקציות Cloud Run הרלוונטיות
- אפשר להשבית את הרישום עצמו באמצעות החרגות, אבל חשוב לזכור שהאירועים הבעייתיים עדיין לא יועברו.