באמצעות Cloud Functions, אתם יכולים לטפל באירועים ב-Cloud Firestore בלי לעדכן את קוד הלקוח. אפשר לבצע שינויים ב-Cloud Firestore דרך ממשק תמונת המצב של המסמך או דרך SDK לאדמינים.
במחזור חיים אופייני, פונקציית Cloud Firestore מבצעת את הפעולות הבאות:
- המתנה לשינויים במסמך מסוים.
- מופעל כשמתרחש אירוע ומבצע את המשימות שלו.
- מקבל אובייקט נתונים שמכיל תמונת מצב של הנתונים שמאוחסנים במסמך שצוין. באירועי כתיבה או עדכון, אובייקט הנתונים מכיל שתי תמונות מצב שמייצגות את מצב הנתונים לפני האירוע שהפעיל את הפונקציה ואחריו.
המרחק בין המיקום של המכונה של Firestore לבין המיקום של הפונקציה יכול ליצור זמן אחזור משמעותי ברשת. כדי לשפר את הביצועים, כדאי לציין את מיקום הפונקציה במקרים הרלוונטיים.
טריגרים של פונקציות ב-Cloud Firestore
ערכת ה-SDK של Cloud Functions for Firebase מייצאת את הטריגרים הבאים של אירועים ב-Cloud Firestore, כדי לאפשר לכם ליצור פונקציות טיפול שמקושרות לאירועים ספציפיים ב-Cloud Firestore:
Node.js
| סוג האירוע | הטריגר |
|---|---|
onDocumentCreated |
מופעל כשמסמך נכתב בפעם הראשונה. |
onDocumentUpdated |
מופעל כשמסמך כבר קיים וערך כלשהו השתנה. |
onDocumentDeleted |
מופעל כשמסמך נמחק. |
onDocumentWritten |
מופעל כשמופעלים onDocumentCreated, onDocumentUpdated או onDocumentDeleted. |
onDocumentCreatedWithAuthContext |
onDocumentCreated עם פרטי אימות נוספים |
onDocumentWrittenWithAuthContext |
onDocumentWritten עם פרטי אימות נוספים |
onDocumentDeletedWithAuthContext |
onDocumentDeleted עם פרטי אימות נוספים |
onDocumentUpdatedWithAuthContext |
onDocumentUpdated עם פרטי אימות נוספים |
Python
| סוג האירוע | הטריגר |
|---|---|
on_document_created |
מופעל כשמסמך נכתב בפעם הראשונה. |
on_document_updated |
מופעל כשמסמך כבר קיים וערך כלשהו השתנה. |
on_document_deleted |
מופעל כשמסמך נמחק. |
on_document_written |
מופעל כשמופעלים on_document_created, on_document_updated או on_document_deleted. |
on_document_created_with_auth_context |
on_document_created עם פרטי אימות נוספים |
on_document_updated_with_auth_context |
on_document_updated עם פרטי אימות נוספים |
on_document_deleted_with_auth_context |
on_document_deleted עם פרטי אימות נוספים |
on_document_written_with_auth_context |
on_document_written עם פרטי אימות נוספים |
אירועים ב-Cloud Firestore מופעלים רק כשמתבצעים שינויים במסמכים. עדכון של מסמך ב-Cloud Firestore שבו הנתונים לא משתנים (כתיבה ללא פעולה) לא יוצר אירוע עדכון או כתיבה. אי אפשר להוסיף אירועים לשדות ספציפיים.
אם עדיין לא הפעלתם פרויקט ל-Cloud Functions for Firebase, קראו את המאמר תחילת השימוש ב-Cloud Functions for Firebase (דור שני) כדי להגדיר את הפרויקט ב-Cloud Functions for Firebase.
כתיבת פונקציות שמופעלות על ידי Cloud Firestore
הגדרת טריגר לפונקציה
כדי להגדיר טריגר של Cloud Firestore, מציינים נתיב למסמך וסוג אירוע:
Node.js
const {
onDocumentWritten,
onDocumentCreated,
onDocumentUpdated,
onDocumentDeleted,
Change,
FirestoreEvent
} = require('firebase-functions/v2/firestore');
exports.myfunction = onDocumentWritten("my-collection/{docId}", (event) => {
/* ... */
});
Python
from firebase_functions.firestore_fn import (
on_document_created,
on_document_deleted,
on_document_updated,
on_document_written,
Event,
Change,
DocumentSnapshot,
)
@on_document_created(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot]) -> None:
נתיבי מסמכים יכולים להפנות למסמך ספציפי או לתבנית של תו כללי לחיפוש.
צריך לציין מסמך אחד
אם רוצים להפעיל אירוע עבור כל שינוי במסמך ספציפי, אפשר להשתמש בפונקציה הבאה.
Node.js
const {
onDocumentWritten,
Change,
FirestoreEvent
} = require('firebase-functions/v2/firestore');
exports.myfunction = onDocumentWritten("users/marie", (event) => {
// Your code here
});
Python
from firebase_functions.firestore_fn import (
on_document_written,
Event,
Change,
DocumentSnapshot,
)
@on_document_written(document="users/marie")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
ציון קבוצת מסמכים באמצעות תווים כלליים לחיפוש
אם רוצים לצרף טריגר לקבוצת מסמכים, כמו כל מסמך באוסף מסוים, משתמשים ב-{wildcard} במקום במזהה המסמך:
Node.js
const {
onDocumentWritten,
Change,
FirestoreEvent
} = require('firebase-functions/v2/firestore');
exports.myfunction = onDocumentWritten("users/{userId}", (event) => {
// If we set `/users/marie` to {name: "Marie"} then
// event.params.userId == "marie"
// ... and ...
// event.data.after.data() == {name: "Marie"}
});
Python
from firebase_functions.firestore_fn import (
on_document_written,
Event,
Change,
DocumentSnapshot,
)
@on_document_written(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
# If we set `/users/marie` to {name: "Marie"} then
event.params["userId"] == "marie" # True
# ... and ...
event.data.after.to_dict() == {"name": "Marie"} # True
בדוגמה הזו, כששדה כלשהו במסמך כלשהו ב-users משתנה, הוא תואם לתו כללי לחיפוש שנקרא userId.
אם למסמך ב-users יש אוספי משנה, ואחד מהשדות במסמכים של אוספי המשנה האלה משתנה, התו הכללי לחיפוש userId לא מופעל.
התאמות של תווים כלליים לחיפוש נשלפות מנתיב המסמך ונשמרות ב-event.params.
אפשר להגדיר כמה תווים כלליים לחיפוש שרוצים כדי להחליף מזהים מפורשים של אוספים או מסמכים, לדוגמה:
Node.js
const {
onDocumentWritten,
Change,
FirestoreEvent
} = require('firebase-functions/v2/firestore');
exports.myfunction = onDocumentWritten("users/{userId}/{messageCollectionId}/{messageId}", (event) => {
// If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
// event.params.userId == "marie";
// event.params.messageCollectionId == "incoming_messages";
// event.params.messageId == "134";
// ... and ...
// event.data.after.data() == {body: "Hello"}
});
Python
from firebase_functions.firestore_fn import (
on_document_written,
Event,
Change,
DocumentSnapshot,
)
@on_document_written(document="users/{userId}/{messageCollectionId}/{messageId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
# If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
event.params["userId"] == "marie" # True
event.params["messageCollectionId"] == "incoming_messages" # True
event.params["messageId"] == "134" # True
# ... and ...
event.data.after.to_dict() == {"body": "Hello"}
הטריגר חייב תמיד להצביע על מסמך, גם אם משתמשים בתו כללי לחיפוש.
לדוגמה, users/{userId}/{messageCollectionId} לא תקין כי {messageCollectionId} הוא אוסף. עם זאת, users/{userId}/{messageCollectionId}/{messageId} תקין כי {messageId} תמיד יצביע על מסמך.
טריגרים לאירועים
הפעלת פונקציה כשנוצר מסמך חדש
אפשר להפעיל פונקציה בכל פעם שנוצר מסמך חדש באוסף. הפונקציה לדוגמה הזו מופעלת בכל פעם שנוסף פרופיל משתמש חדש:
Node.js
const {
onDocumentCreated,
Change,
FirestoreEvent
} = require('firebase-functions/v2/firestore');
exports.createuser = onDocumentCreated("users/{userId}", (event) => {
// Get an object representing the document
// e.g. {'name': 'Marie', 'age': 66}
const snapshot = event.data;
if (!snapshot) {
console.log("No data associated with the event");
return;
}
const data = snapshot.data();
// access a particular field as you would any JS property
const name = data.name;
// perform more operations ...
});
כדי לקבל מידע נוסף על אימות, אפשר להשתמש ב-onDocumentCreatedWithAuthContext.
Python
from firebase_functions.firestore_fn import (
on_document_created,
Event,
DocumentSnapshot,
)
@on_document_created(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot]) -> None:
# Get a dictionary representing the document
# e.g. {'name': 'Marie', 'age': 66}
new_value = event.data.to_dict()
# Access a particular field as you would any dictionary
name = new_value["name"]
# Perform more operations ...
הפעלת פונקציה כשמסמך מתעדכן
אפשר גם להגדיר פונקציה שתופעל כשמסמך מתעדכן. הפונקציה לדוגמה הזו מופעלת אם משתמש משנה את הפרופיל שלו:
Node.js
const {
onDocumentUpdated,
Change,
FirestoreEvent
} = require('firebase-functions/v2/firestore');
exports.updateuser = onDocumentUpdated("users/{userId}", (event) => {
// Get an object representing the document
// e.g. {'name': 'Marie', 'age': 66}
const newValue = event.data.after.data();
// access a particular field as you would any JS property
const name = newValue.name;
// perform more operations ...
});
כדי לקבל מידע נוסף על אימות, אפשר להשתמש ב-onDocumentUpdatedWithAuthContext.
Python
from firebase_functions.firestore_fn import (
on_document_updated,
Event,
Change,
DocumentSnapshot,
)
@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
# Get a dictionary representing the document
# e.g. {'name': 'Marie', 'age': 66}
new_value = event.data.after.to_dict()
# Access a particular field as you would any dictionary
name = new_value["name"]
# Perform more operations ...
הפעלת פונקציה כשמסמך נמחק
אפשר גם להפעיל פונקציה כשמסמך נמחק. הפונקציה לדוגמה הזו מופעלת כשמשתמש מוחק את פרופיל המשתמש שלו:
Node.js
const {
onDocumentDeleted,
Change,
FirestoreEvent
} = require('firebase-functions/v2/firestore');
exports.deleteuser = onDocumentDeleted("users/{userId}", (event) => {
// Get an object representing the document
// e.g. {'name': 'Marie', 'age': 66}
const snap = event.data;
const data = snap.data();
// perform more operations ...
});
כדי לקבל מידע נוסף על אימות, אפשר להשתמש ב-onDocumentDeletedWithAuthContext.
Python
from firebase_functions.firestore_fn import (
on_document_deleted,
Event,
DocumentSnapshot,
)
@on_document_deleted(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot|None]) -> None:
# Perform more operations ...
הפעלת פונקציה לכל השינויים במסמך
אם לא אכפת לכם מסוג האירוע שמופעל, אתם יכולים להאזין לכל השינויים במסמך Cloud Firestore באמצעות הטריגר של האירוע "מסמך נכתב". הפונקציה לדוגמה הזו מופעלת אם משתמש נוצר, מעודכן או נמחק:
Node.js
const {
onDocumentWritten,
Change,
FirestoreEvent
} = require('firebase-functions/v2/firestore');
exports.modifyuser = onDocumentWritten("users/{userId}", (event) => {
// Get an object with the current document values.
// If the document does not exist, it was deleted
const document = event.data.after.data();
// Get an object with the previous document values
const previousValues = event.data.before.data();
// perform more operations ...
});
כדי לקבל מידע נוסף על אימות, אפשר להשתמש ב-onDocumentWrittenWithAuthContext.
Python
from firebase_functions.firestore_fn import (
on_document_written,
Event,
Change,
DocumentSnapshot,
)
@on_document_written(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot | None]]) -> None:
# Get an object with the current document values.
# If the document does not exist, it was deleted.
document = (event.data.after.to_dict()
if event.data.after is not None else None)
# Get an object with the previous document values.
# If the document does not exist, it was newly created.
previous_values = (event.data.before.to_dict()
if event.data.before is not None else None)
# Perform more operations ...
קריאה וכתיבה של נתונים
כשפונקציה מופעלת, היא מספקת תמונת מצב של הנתונים שקשורים לאירוע. אתם יכולים להשתמש בתמונת המצב הזו כדי לקרוא מהמסמך שהפעיל את האירוע או לכתוב אליו, או להשתמש ב-SDK של Firebase לאדמינים כדי לגשת לחלקים אחרים במסד הנתונים.
נתוני אירוע
נתוני הקריאה
כשפונקציה מופעלת, יכול להיות שתרצו לקבל נתונים ממסמך שעודכן, או לקבל את הנתונים לפני העדכון. כדי לקבל את הנתונים הקודמים, אפשר להשתמש ב-event.data.before, שמכיל את תמונת המצב של המסמך לפני העדכון.
באופן דומה, event.data.after מכיל את מצב תמונת המצב של המסמך אחרי העדכון.
Node.js
exports.updateuser2 = onDocumentUpdated("users/{userId}", (event) => {
// Get an object with the current document values.
// If the document does not exist, it was deleted
const newValues = event.data.after.data();
// Get an object with the previous document values
const previousValues = event.data.before.data();
});
Python
@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
# Get an object with the current document values.
new_value = event.data.after.to_dict()
# Get an object with the previous document values.
prev_value = event.data.before.to_dict()
אפשר לגשת למאפיינים כמו בכל אובייקט אחר. לחלופין, אתם יכולים להשתמש בפונקציה get כדי לגשת לשדות ספציפיים:
Node.js
// Fetch data using standard accessors
const age = event.data.after.data().age;
const name = event.data.after.data()['name'];
// Fetch data using built in accessor
const experience = event.data.after.data.get('experience');
Python
# Get the value of a single document field.
age = event.data.after.get("age")
# Convert the document to a dictionary.
age = event.data.after.to_dict()["age"]
כתיבת נתונים
כל הפעלה של פונקציה משויכת למסמך ספציפי במסד הנתונים של Cloud Firestore. אפשר לגשת למסמך הזה בתמונת המצב שמוחזרת לפונקציה.
ההפניה למסמך כוללת שיטות כמו update(), set() ו-remove(), כדי שתוכלו לשנות את המסמך שהפעיל את הפונקציה.
Node.js
const {onDocumentUpdated} = require('firebase-functions/v2/firestore');
exports.countnamechanges = onDocumentUpdated('users/{userId}', (event) => {
// Retrieve the current and previous value
const data = event.data.after.data();
const previousData = event.data.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 event.data.after.ref.set({
name_change_count: count + 1
}, {merge: true});
});
Python
@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
# Get the current and previous document values.
new_value = event.data.after
prev_value = event.data.before
# We'll only update if the name has changed.
# This is crucial to prevent infinite loops.
if new_value.get("name") == prev_value.get("name"):
return
# Retrieve the current count of name changes
count = new_value.to_dict().get("name_change_count", 0)
# Update the count
new_value.reference.update({"name_change_count": count + 1})
גישה לפרטי אימות המשתמשים
אם אתם משתמשים באחד מסוגי האירועים הבאים, אתם יכולים לגשת לפרטי אימות המשתמש של חשבון המשתמש שהפעיל את האירוע. המידע הזה מתווסף למידע שמוחזר באירוע הבסיסי.
Node.js
onDocumentCreatedWithAuthContextonDocumentWrittenWithAuthContextonDocumentDeletedWithAuthContextonDocumentUpdatedWithAuthContext
Python
on_document_created_with_auth_contexton_document_updated_with_auth_contexton_document_deleted_with_auth_contexton_document_written_with_auth_context
למידע על הנתונים שזמינים בהקשר לצורכי אימות, קראו את המאמר הקשר לצורכי אימות. בדוגמה הבאה אפשר לראות איך לאחזר את פרטי האימות:
Node.js
const {onDocumentWrittenWithAuthContext} = require('firebase-functions/v2/firestore');
exports.syncUser = onDocumentWrittenWithAuthContext("users/{userId}", (event) => {
const snapshot = event.data.after;
if (!snapshot) {
console.log("No data associated with the event");
return;
}
const data = snapshot.data();
// retrieve auth context from event
const { authType, authId } = event;
let verified = false;
if (authType === "system") {
// system-generated users are automatically verified
verified = true;
} else if (authType === "unknown" || authType === "unauthenticated") {
// admin users from a specific domain are verified
if (authId.endsWith("@example.com")) {
verified = true;
}
}
return data.after.ref.set({
created_by: authId,
verified,
}, {merge: true});
});
Python
@on_document_updated_with_auth_context(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
# Get the current and previous document values.
new_value = event.data.after
prev_value = event.data.before
# Get the auth context from the event
user_auth_type = event.auth_type
user_auth_id = event.auth_id
נתונים מחוץ לאירוע הטריגר
Cloud Functions פועלים בסביבה מהימנה. הם מורשים כחשבון שירות בפרויקט שלכם, ואתם יכולים לבצע פעולות קריאה וכתיבה באמצעות ה-SDK של Firebase לאדמינים:
Node.js
const { initializeApp } = require('firebase-admin/app');
const { getFirestore, Timestamp, FieldValue } = require('firebase-admin/firestore');
initializeApp();
const db = getFirestore();
exports.writetofirestore = onDocumentWritten("some/doc", (event) => {
db.doc('some/otherdoc').set({ ... });
});
exports.writetofirestore = onDocumentWritten('users/{userId}', (event) => {
db.doc('some/otherdoc').set({
// Update otherdoc
});
});
Python
from firebase_admin import firestore, initialize_app
import google.cloud.firestore
initialize_app()
@on_document_written(document="some/doc")
def myfunction(event: Event[Change[DocumentSnapshot | None]]) -> None:
firestore_client: google.cloud.firestore.Client = firestore.client()
firestore_client.document("another/doc").set({
# ...
})
מגבלות
שימו לב למגבלות הבאות של טריגרים של Cloud Firestore ל-Cloud Functions:
- כדי להשתמש ב-Cloud Functions (דור ראשון), צריך מסד נתונים קיים מסוג (default) במצב המקורי של Firestore. הוא לא תומך במסדי נתונים בעלי שם ב-Cloud Firestore או במצב Datastore. במקרים כאלה, צריך להשתמש ב-Cloud Functions (דור שני) כדי להגדיר אירועים.
- הגדרה של פרויקטים שונים עם טריגר של Cloud Functions ו-Cloud Firestore היא מגבלה. כדי להגדיר את Cloud Firestore, הטריגר Cloud Functions צריך להיות באותו פרויקט.
- לא מובטח שההזמנה תתבצע. שינויים מהירים יכולים להפעיל פונקציות בסדר לא צפוי.
- אירועים נשלחים לפחות פעם אחת, אבל אירוע יחיד יכול לגרום להפעלה של כמה פונקציות. אל תסתמכו על מנגנונים של "בדיוק פעם אחת", וכתבו פונקציות אידמפוטנטיות.
- כדי להשתמש ב-Cloud Firestore במצב Datastore צריך את Cloud Functions (דור שני). Cloud Functions (דור ראשון) לא תומך במצב Datastore.
- טריגר משויך למסד נתונים אחד. אי אפשר ליצור טריגר שתואם לכמה מסדי נתונים.
- מחיקה של מסד נתונים לא מוחקת באופן אוטומטי את הטריגרים של מסד הנתונים הזה. הטריגר מפסיק להעביר אירועים, אבל ממשיך להתקיים עד שמוחקים אותו.
- אם אירוע תואם חורג מגודל הבקשה המקסימלי, יכול להיות שהאירוע לא יועבר אל Cloud Functions (דור ראשון).
- אירועים שלא נמסרו בגלל גודל הבקשה מתועדים ביומני הפלטפורמה ונכללים בשימוש ביומן של הפרויקט.
- היומנים האלה מופיעים ב-Logs Explorer עם ההודעה "Event cannot deliver to Cloud function due to size exceeding the limit for דור ראשון..." ברמת החומרה
error. שם הפונקציה מופיע בשדהfunctionName. אם השדהreceiveTimestampעדיין נמצא בטווח של שעה מעכשיו, אפשר להסיק מהו תוכן האירוע בפועל על ידי קריאת המסמך הרלוונטי עם תמונת מצב לפני חותמת הזמן ואחריה. - כדי למנוע מצב כזה, אתם יכולים:
- מעבר ושדרוג ל-Cloud Functions (דור שני)
- הקטנת המסמך
- מוחקים את ה-Cloud Functions הרלוונטי.
- אפשר להשבית את הרישום עצמו באמצעות החרגות, אבל שימו לב שהאירועים הבעייתיים עדיין לא יועברו.