בעזרת Cloud Functions, אתם יכולים לטפל באירועים ב-Firebase Realtime Database בלי לעדכן את קוד הלקוח. Cloud Functions מאפשר להריץ פעולות של Realtime Database עם הרשאות אדמין מלאות, ומוודא שכל שינוי ב-Realtime Database מעובד בנפרד. אפשר לבצע שינויים ב-Firebase Realtime Database דרך תמונת המצב של הנתונים או דרך SDK לאדמינים.
במחזור חיים אופייני, פונקציית Firebase Realtime Database מבצעת את הפעולות הבאות:
- המתנה לשינויים בנתיב Realtime Database מסוים.
- מופעל כשמתרחש אירוע ומבצע את המשימות שלו.
- מקבל אובייקט נתונים שמכיל תמונת מצב של הנתונים שמאוחסנים בנתיב הזה.
אפשר להפעיל פונקציה בתגובה לכתיבה, ליצירה, לעדכון או למחיקה של צומתי מסד נתונים ב-Firebase Realtime Database. כדי לקבוע מתי הפונקציה תופעל, צריך לציין את אחד מהגורמים המטפלים באירועים ואת הנתיב ב-Realtime Database שבו הוא יאזין לאירועים.
הגדרת המיקום של הפונקציה
המרחק בין המיקום של מכונת Realtime Database לבין המיקום של הפונקציה יכול ליצור זמן אחזור משמעותי ברשת. בנוסף, חוסר התאמה בין האזורים עלול לגרום לפריסה להיכשל. כדי להימנע ממצבים כאלה, צריך לציין את מיקום הפונקציה כך שיתאים למיקום של מופע מסד הנתונים.
טיפול באירועי Realtime Database
פונקציות מאפשרות לכם לטפל באירועים של Realtime Database בשתי רמות של ספציפיות: אתם יכולים להאזין באופן ספציפי רק לאירועי כתיבה, יצירה, עדכון או מחיקה, או להאזין לכל שינוי מכל סוג שהוא בהפניה.
אלה הגורמים המטפלים שזמינים לתגובה לאירועים של Realtime Database:
Node.js
-
onValueWritten()מופעל כשנתונים נוצרים, מתעדכנים או נמחקים ב-Realtime Database. -
onValueCreated()מופעל רק כשנוצרים נתונים ב-Realtime Database. -
onValueUpdated()מופעל רק כשהנתונים מתעדכנים ב-Realtime Database. -
onValueDeleted()מופעל רק כשנתונים נמחקים ב-Realtime Database.
Python
on_value_written()מופעל כשנוצרים, מתעדכנים או נמחקים נתונים ב-Realtime Database.-
on_value_created()מופעל רק כשנוצרים נתונים ב-Realtime Database. -
on_value_updated()מופעל רק כשהנתונים מתעדכנים ב-Realtime Database. -
on_value_deleted()מופעל רק כשנתונים נמחקים ב-Realtime Database.
ייבוא המודולים הנדרשים
במקור הפונקציה, צריך לייבא את המודולים של ה-SDK שבהם רוצים להשתמש. בדוגמה הזו צריך לייבא את המודולים HTTP ו-Realtime Database, יחד עם המודול Firebase Admin SDK לכתיבה ב-Realtime Database.
Node.js
// The Cloud Functions for Firebase SDK to setup triggers and logging.
const {onRequest} = require("firebase-functions/https");
const {onValueCreated} = require("firebase-functions/database");
const {logger} = require("firebase-functions");
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require("firebase-admin");
admin.initializeApp();
Python
# The Cloud Functions for Firebase SDK to create Cloud Functions and set up triggers.
from firebase_functions import db_fn, https_fn
# The Firebase Admin SDK to access the Firebase Realtime Database.
from firebase_admin import initialize_app, db
app = initialize_app()
מציינים את המכונה והנתיב
כדי לקבוע מתי ואיפה הפונקציה תופעל, מגדירים אותה עם נתיב ואם רוצים, גם עם מכונה של Realtime Database. אם לא מציינים מכונה, הפונקציה מאזינה לכל המכונות של Realtime Database באזור של הפונקציה. אפשר גם לציין תבנית של מכונות Realtime Database כדי לפרוס בקבוצת משנה סלקטיבית של מכונות באותו אזור.
לדוגמה:
Node.js
// All Realtime Database instances at path "/user/{uid}" // There must be at least one Realtime Database present. const onWrittenFunctionDefault = onValueWritten("/user/{uid}", (event) => { // … }); // Instance named "my-app-db-2", at path "/user/{uid}". // The "my-app-db-2" instance must exist in this region. const OnWrittenFunctionInstance = onValueWritten( { ref: "/user/{uid}", instance: "my-app-db-2" }, (event) => { // … } ); // Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com. // There must be at least one Realtime Database with "my-app-db-*" prefix in this region. const onWrittenFunctionInstance = onValueWritten( { ref: "/user/{uid=*@gmail.com}", instance: "my-app-db-*" }, (event) => { // … } );
Python
# All Realtime Database instances at path "/user/{uid}"
# There must be at least one Realtime Database present.
@db_fn.on_value_written(r"/user/{uid}")
def onwrittenfunctiondefault(event: db_fn.Event[db_fn.Change]):
# ...
pass
# Instance named "my-app-db-2", at path "/user/{uid}".
# The "my-app-db-2" instance must exist in this region.
@db_fn.on_value_written(
reference=r"/user/{uid}",
instance="my-app-db-2",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
# ...
pass
# Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com.
# There must be at least one Realtime Database with "my-app-db-*" prefix in this region.
@db_fn.on_value_written(
reference=r"/user/{uid=*@gmail.com}",
instance="my-app-db-*",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
# ...
pass
הפרמטרים האלה מנחים את הפונקציה לטפל בכתיבה בנתיב מסוים בתוך המכונה של Realtime Database.
מפרטי הנתיבים תואמים לכל פעולות הכתיבה שנוגעות בנתיב, כולל פעולות כתיבה שמתרחשות בכל מקום מתחתיו. אם תגדירו את הנתיב של הפונקציה כ-/foo/bar, היא תתאים לאירועים בשני המיקומים הבאים:
/foo/bar
/foo/bar/baz/really/deep/path
בשני המקרים, מערכת Firebase מפרשת שהאירוע מתרחש ב-/foo/bar, ונתוני האירוע כוללים את הנתונים הישנים והחדשים ב-/foo/bar. אם נתוני האירוע עשויים להיות גדולים, כדאי להשתמש בכמה פונקציות בנתיבים עמוקים יותר במקום בפונקציה אחת ליד הבסיס של מסד הנתונים. כדי לקבל את הביצועים הטובים ביותר, בקשו נתונים רק ברמה העמוקה ביותר שאפשר.
תו כללי לחיפוש וצילום
אפשר להשתמש ב-{key}, {key=*}, {key=prefix*}, {key=*suffix} לצילום. *, prefix*, *suffix לתו כללי לחיפוש של פלח יחיד.
הערה: ** מייצג תו כללי לחיפוש של כמה פלחים, שאין בו תמיכה ב-Realtime Database.
הסבר על תבניות נתיבים
תווים כלליים לחיפוש בנתיב. אפשר לציין רכיב נתיב כתו כללי לחיפוש:
- באמצעות כוכבית,
*. לדוגמה,foo/*תואם לכל הצאצאים ברמה אחת של היררכיית הצמתים מתחת ל-foo/. - שימוש בפלח שמכיל בדיוק כוכבית,
*. לדוגמה,foo/app*-usתואם לכל פלחי הצאצא מתחת ל-foo/עם הקידומתappוהסיומת-us.
נתיבים עם תווים כלליים לחיפוש יכולים להתאים לכמה אירועים, למשל, מכתיבה אחת. הוספה של
{
"foo": {
"hello": "world",
"firebase": "functions"
}
}
תואם לנתיב "/foo/*" פעמיים: פעם אחת עם "hello": "world" ופעם נוספת עם "firebase": "functions".
תיעוד הנתיב. אפשר לתעד התאמות של נתיבים במשתנים בעלי שם כדי להשתמש בהם בקוד הפונקציה (למשל /user/{uid}, /user/{uid=*-us}).
הערכים של משתני התיעוד זמינים באובייקט database.DatabaseEvent.params של הפונקציה.
תווים כלליים לחיפוש מופעים אפשר גם לציין רכיב של מכונה באמצעות תו כללי לחיפוש. תו כללי לחיפוש של מופע יכול לכלול תחילית, סיומת או את שתיהן (למשל my-app-*-prod).
הפניה לתווים כלליים לחיפוש ולתיעוד
ב-Cloud Functions (דור שני) וב-Realtime Database, אפשר להשתמש בתבנית כשמציינים את ref ואת instance. בכל ממשק של טריגר יהיו האפשרויות הבאות להגדרת היקף הפונקציה:
הגדרת ref |
הגדרת instance |
התנהגות |
|---|---|---|
סינגל (/foo/bar) |
לא רוצה לציין | מגדיר את ה-handler לכל המכונות באזור של הפונקציה. |
סינגל (/foo/bar) |
סינגל (‘my-new-db') |
מגדיר את היקף ה-handler למכונה הספציפית באזור של הפונקציה. |
סינגל (/foo/bar) |
דוגמת עיצוב (‘inst-prefix*') |
מגדיר את היקף ה-handler לכל המופעים שתואמים לתבנית באזור הפונקציה. |
דוגמת עיצוב (/foo/{bar}) |
לא רוצה לציין | מגדיר את ה-handler לכל המכונות באזור של הפונקציה. |
דוגמת עיצוב (/foo/{bar}) |
סינגל (‘my-new-db') |
מגדיר את ה-handler למופע הספציפי באזור של הפונקציה. |
דוגמת עיצוב (/foo/{bar}) |
דוגמת עיצוב (‘inst-prefix*') |
מגדיר את היקף ה-handler לכל המופעים שתואמים לתבנית באזור הפונקציה. |
טיפול בנתוני אירועים
כשמופעל אירוע Realtime Database, הוא מעביר אובייקט Event לפונקציית ה-handler.
לאובייקט הזה יש מאפיין data, שבאירועי יצירה ומחיקה מכיל תמונת מצב של הנתונים שנוצרו או נמחקו.
בדוגמה הזו, הפונקציה מאחזרת את הנתונים של הנתיב שאליו מפנים, ממירה את המחרוזת במיקום הזה לאותיות רישיות וכותבת את המחרוזת ששונתה במסד הנתונים:
Node.js
// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
// for all databases in 'us-central1'
exports.makeuppercase = onValueCreated(
"/messages/{pushId}/original",
(event) => {
// Grab the current value of what was written to the Realtime Database.
const original = event.data.val();
logger.log("Uppercasing", event.params.pushId, original);
const uppercase = original.toUpperCase();
// You must return a Promise when performing
// asynchronous tasks inside a function, such as
// writing to the Firebase Realtime Database.
// Setting an "uppercase" sibling in the
// Realtime Database returns a Promise.
return event.data.ref.parent.child("uppercase").set(uppercase);
},
);
Python
@db_fn.on_value_created(reference="/messages/{pushId}/original")
def makeuppercase(event: db_fn.Event[Any]) -> None:
"""Listens for new messages added to /messages/{pushId}/original and
creates an uppercase version of the message to /messages/{pushId}/uppercase
"""
# Grab the value that was written to the Realtime Database.
original = event.data
if not isinstance(original, str):
print(f"Not a string: {event.reference}")
return
# Use the Admin SDK to set an "uppercase" sibling.
print(f"Uppercasing {event.params['pushId']}: {original}")
upper = original.upper()
parent = db.reference(event.reference).parent
if parent is None:
print("Message can't be root node.")
return
parent.child("uppercase").set(upper)
קריאת הערך הקודם
באירועים מסוג write או update, המאפיין data הוא אובייקט Change שמכיל שתי תמונות מצב שמייצגות את מצב הנתונים לפני ואחרי האירוע שהפעיל את הפונקציה.
לאובייקט Change יש מאפיין before שמאפשר לבדוק מה נשמר ב-Realtime Database לפני האירוע, ומאפיין after שמייצג את מצב הנתונים אחרי שהאירוע התרחש.
לדוגמה, אפשר להשתמש במאפיין before כדי לוודא שהפונקציה משנה את הטקסט לאותיות רישיות רק כשהוא נוצר בפעם הראשונה:
Node.js
exports makeUppercase = onValueWritten("/messages/{pushId}/original", (event) => { // Only edit data when it is first created. if (event.data.before.exists()) { return null; } // Exit when the data is deleted. if (!event.data.after.exists()) { return null; } // Grab the current value of what was written to the Realtime Database. const original = event.data.after.val(); console.log('Uppercasing', event.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 event.data.after.ref.parent.child('uppercase').set(uppercase); });
Python
@db_fn.on_value_written(reference="/messages/{pushId}/original")
def makeuppercase2(event: db_fn.Event[db_fn.Change]) -> None:
"""Listens for new messages added to /messages/{pushId}/original and
creates an uppercase version of the message to /messages/{pushId}/uppercase
"""
# Only edit data when it is first created.
if event.data.before is not None:
return
# Exit when the data is deleted.
if event.data.after is None:
return
# Grab the value that was written to the Realtime Database.
original = event.data.after
if not hasattr(original, "upper"):
print(f"Not a string: {event.reference}")
return
# Use the Admin SDK to set an "uppercase" sibling.
print(f"Uppercasing {event.params['pushId']}: {original}")
upper = original.upper()
parent = db.reference(event.reference).parent
if parent is None:
print("Message can't be root node.")
return
parent.child("uppercase").set(upper)
הקשר לצורכי אימות גישה
בפונקציות שמופעלות על ידי אירועי Eventarc של RTDB, ההקשר לצורכי אימות נכלל במטען הייעודי (payload) של האירוע:
- authtype: סוג חשבון המשתמש שהפעיל את האירוע. הערכים האפשריים הם:
-
app_user: משתמש קצה באפליקציה של המפתח. -
admin: חשבון שירות. -
unauthenticated: משתמש לא מאומת. -
unknown: ברירת המחדל כשפרטי האימות לא זמינים.
-
- authid: המזהה הייחודי של חשבון המשתמש.
- אם הערך של
authtypeהואapp_user, זהו מזהה המשתמש. - אם הערך של
authtypeהואadmin, זהו חשבון השירות או כתובת האימייל של המשתמש ב-IAM.
- אם הערך של
הקוד הזה משנה את הטקסט של ההודעה לאותיות רישיות רק אם המשתמש שהפעיל את הפונקציה הוא לא אדמין. בנוסף, הקוד בודק אם המשתמש שהפעיל את ההודעה הוא השולח בפועל של ההודעה.
Node.js
// The Cloud Functions for Firebase SDK to setup triggers and logging.
const {onValueWritten} = require("firebase-functions/v2/database");
const {logger} = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.dbtrigger = onValueWritten("/messages/{pushId}/original", async (event) => {
// 1. Check whether authtype is admin. If it is, skip this operation.
if (event.authType === "admin") {
logger.log("Modification by admin detected. Skipping uppercase conversion.");
return null;
}
// 2. Retrieve the userID of the sender (assumed sibling node 'senderId')
const snapshot = await event.data.after.ref.parent.child("senderId").get();
const senderId = snapshot.val();
// 3. Check if userID of sender of message = event.authid
if (senderId !== event.authId) {
logger.error(`Unauthorized write: senderId (${senderId}) does not match authId (${event.authId})`);
return null;
}
// Grab the value that was written to the Realtime Database.
const original = event.data.after.val();
logger.log("Uppercasing", event.params.pushId, original);
const uppercase = original.toUpperCase();
// Return the promise to set the "uppercase" sibling node.
return event.data.after.ref.parent.child("uppercase").set(uppercase);
});
Python
from firebase_functions import db_fn
from firebase_admin import initialize_app, db
initialize_app()
@db_fn.on_value_written(reference="/messages/{pushId}/original")
def makeuppercase(event: db_fn.Event[db_fn.Change]) -> None:
# 1. Check whether authtype is admin. If it is, skip this operation.
if event.auth_type == "admin":
print("Admin user detected. Skipping.")
return
# 2. Retrieve the userID of the sender (assumed sibling node: 'senderId')
parent_ref = db.reference(event.reference).parent
sender_id = parent_ref.child("senderId").get()
# 3. Check if userID of sender = event.auth_id
if sender_id != event.auth_id:
print(f"Unauthorized: sender_id {sender_id} != auth_id {event.auth_id}")
return
# Exit when the data is deleted.
if event.data.after is None:
return
# Grab the value and uppercase it
original = event.data.after
if not isinstance(original, str):
return
print(f"Uppercasing {event.params['pushId']}: {original}")
upper = original.upper()
parent_ref.child("uppercase").set(upper)