בעזרת Cloud Functions אפשר לטפל באירועים ב-Firebase Realtime Database בלי צורך לעדכן את קוד הלקוח. Cloud Functions מאפשר להריץ פעולות של Realtime Database עם הרשאות אדמין מלאות, ומבטיח שכל שינוי ב-Realtime Database יטופל בנפרד. אפשר לבצע שינויים ב-Firebase Realtime Database דרך קובץ snapshot של הנתונים או דרך Admin SDK.
במחזור חיים אופייני, פונקציית Firebase Realtime Database מבצעת את הפעולות הבאות:
- הפונקציה ממתינה לשינויים בנתיב Realtime Database מסוים.
- מופעל כשמתרחש אירוע ומבצע את המשימות שלו.
- מקבל אובייקט נתונים שמכיל קובץ snapshot של הנתונים שמאוחסנים בנתיב הזה.
אפשר להפעיל פונקציה בתגובה לכתיבת צמתים במסד נתונים, ליצירתם, לעדכון שלהם או למחיקה שלהם ב-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/v2/https");
const {onValueCreated} = require("firebase-functions/v2/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 in default function region us-central1 at path "/user/{uid}" // There must be at least one Realtime Database present in us-central1. 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" // This example assumes us-central1, but to set location: // region: "europe-west1" }, (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-*" // This example assumes us-central1, but to set location: // region: "europe-west1" }, (event) => { // … } );
Python
# All Realtime Database instances in default function region us-central1 at path "/user/{uid}"
# There must be at least one Realtime Database present in us-central1.
@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",
# This example assumes us-central1, but to set location:
# region="europe-west1",
)
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-*",
# This example assumes us-central1, but to set location:
# region="europe-west1",
)
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
. אם נתוני האירועים עשויים להיות גדולים, מומלץ להשתמש במספר פונקציות בנתיב עמוק יותר במקום בפונקציה אחת ליד הבסיס של מסד הנתונים. כדי לקבל את הביצועים הטובים ביותר, כדאי לבקש נתונים רק ברמה העמוקה ביותר האפשרית.
שימוש בתווים כלליים לחיפוש ושימוש ב-Capture
אפשר להשתמש ב-{key}
, {key=*}
, {key=prefix*}
, {key=*suffix}
לצילום. *
, prefix*
, *suffix
לשימוש בתו אסימון ריבוע (wildcard) בפלחים בודדים.
הערה: **
מייצג תו כללי לחיפוש בכמה פלחים, שאין תמיכה בו ב-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 ) |
לא מציינים | הטיפול בהיקפים לכל המכונות באזור הפונקציה. |
יחיד (/foo/bar ) |
יחיד (‘my-new-db' ) |
הטיפול בהיקפים מוגדר למכונה הספציפית באזור הפונקציה. |
יחיד (/foo/bar ) |
דפוס (‘inst-prefix*' ) |
הקצאת הטיפול לכל המופעים שתואמים לדפוס באזור הפונקציה. |
דפוס (/foo/{bar} ) |
לא מציינים | הטיפול בהיקפים לכל המכונות באזור הפונקציה. |
דפוס (/foo/{bar} ) |
יחיד (‘my-new-db' ) |
הטיפול בהיקפים מוגדר למכונה הספציפית באזור הפונקציה. |
דפוס (/foo/{bar} ) |
דפוס (‘inst-prefix*' ) |
הקצאת הטיפול לכל המופעים שתואמים לדפוס באזור הפונקציה. |
טיפול בנתוני אירועים
כשאירוע Realtime Database מופעל, הוא מעביר אובייקט Event
לפונקציית הטיפול.
לאובייקט הזה יש מאפיין data
, שמכיל קובץ snapshot של הנתונים שנוצרו או נמחקו באירועי יצירה ומחיקה.
בדוגמה הזו, הפונקציה מאחזרת את הנתונים של הנתיב שצוין, ממירה את המחרוזת במיקום הזה לאותיות רישיות ומזינה את המחרוזת המתוקנת בבסיס הנתונים:
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
שמכיל שתי קובצי snapshot שמייצגים את מצב הנתונים לפני ואחרי האירוע שהפעיל אותם.
לאובייקט 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)