עם Cloud Functions, אתם יכולים לטפל באירועים ב-Firebase Realtime Database בלי לעדכן את קוד הלקוח.
Cloud Functions מאפשר להריץ פעולות של Realtime Database עם הרשאות אדמיניסטרטיביות מלאות, ומוודא שכל שינוי ב-Realtime Database יעבור עיבוד בנפרד. אפשר לבצע שינויים ב-Firebase Realtime Database דרך DataSnapshot
או דרך Admin SDK.
במחזור חיים טיפוסי, פונקציית Firebase Realtime Database מבצעת את הפעולות הבאות:
- הפונקציה ממתינה לשינויים בRealtime Databaseמיקום מסוים.
- הטריגר מופעל כשמתרחש אירוע ומבצע את המשימות שלו (ראו מה אפשר לעשות עם Cloud Functions? לדוגמאות של תרחישי שימוש).
- מקבל אובייקט נתונים שמכיל תמונת מצב של הנתונים שמאוחסנים במסמך שצוין.
הפעלת פונקציית 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);
}
});
בנוסף, אפשר להשתמש בפרטי האימות של המשתמש כדי להתחזות למשתמש ולבצע פעולות כתיבה בשמו. כדי למנוע בעיות של פעולות מקבילות, חשוב למחוק את מופע האפליקציה כמו שמוצג בהמשך:
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);
});