הרחבת Cloud Firestore עם תאימות ל-MongoDB באמצעות Cloud Functions (דור שני)

באמצעות Cloud Functions, אתם יכולים לפרוס קוד לטיפול באירועים שמופעלים על ידי שינויים ב-Cloud Firestore עם מסד נתונים של תאימות ל-MongoDB. כך תוכלו להוסיף פונקציונליות בצד השרת לאפליקציה בלי להפעיל שרתים משלכם.

Cloud Functions (דור שני)

Cloud Functions for Firebase (דור שני) מבוסס על Cloud Run ועל Eventarc, ומספק תשתית חזקה יותר, שליטה מתקדמת בביצועים ובמדרגיות ושליטה רבה יותר בזמן הריצה של הפונקציות. מידע נוסף על דור שני זמין במאמר בנושא Cloud Functions for Firebase (דור שני).

טריגרים של פונקציות ב-Cloud Firestore עם תאימות ל-MongoDB

Cloud Functions for Firebase SDK מייצא את טריגרים האירועים הבאים של Cloud Firestore עם תאימות ל-MongoDB, כדי לאפשר לכם ליצור פונקציות handler שקשורות לאירועים ספציפיים של Cloud Firestore עם תאימות ל-MongoDB:

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 עם תאימות ל-MongoDB מופעלים רק כשמתבצעים שינויים במסמך. עדכון של מסמך ב-Cloud Firestore עם תאימות ל-MongoDB שבו הנתונים לא משתנים (פעולת כתיבה ללא שינוי) לא יוצר עדכון או אירוע כתיבה. אי אפשר להוסיף אירועים לשדות ספציפיים.

אם עדיין לא הפעלתם פרויקט ל-Cloud Functions for Firebase, כדאי לקרוא את המאמר תחילת העבודה עם Cloud Functions for Firebase (דור שני) כדי להגדיר את הפרויקט שלכם ב-Cloud Functions for Firebase.

כתיבה של פונקציות שמופעלות על ידי תאימות ל-MongoDB ב-Cloud Firestore

הגדרת טריגר של פונקציה

כדי להגדיר טריגר של Cloud Firestore עם תאימות ל-MongoDB, צריך לציין נתיב מסמך וסוג אירוע:

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.

התאמות של תווים כלליים לחיפוש מחולצות מנתיב המסמך ונשמרות ב-event.params. הטריגר חייב תמיד להפנות למסמך, גם אם משתמשים בתו כללי.

טריגרים לאירועים

הפעלת פונקציה כשנוצר מסמך חדש

אפשר להפעיל פונקציה בכל פעם שנוצר מסמך חדש באוסף. הפונקציה בדוגמה הזו מופעלת בכל פעם שמוסיפים פרופיל משתמש חדש:

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 ...

הפעלת פונקציה לכל השינויים במסמך

אם לא חשוב לכם סוג האירוע שמופעל, אתם יכולים להאזין לכל השינויים במסמך עם תאימות ל-MongoDB ב-Cloud Firestore באמצעות טריגר האירוע document written. הפונקציה הבאה מופעלת אם משתמש נוצר, מתעדכן או נמחק:

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 ...

קריאה וכתיבה של נתונים

כשפונקציה מופעלת, היא מספקת תמונת מצב של הנתונים שקשורים לאירוע. אפשר להשתמש בתמונת המצב הזו כדי לקרוא מהמסמך שהפעיל את האירוע או לכתוב בו, או להשתמש ב-Firebase Admin SDK כדי לגשת לחלקים אחרים במסד הנתונים.

נתוני אירוע

נתוני הקריאה

כשפונקציה מופעלת, יכול להיות שתרצו לקבל נתונים ממסמך שעודכן, או לקבל את הנתונים לפני העדכון. אפשר לקבל את הנתונים הקודמים באמצעות 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 עם תאימות ל-MongoDB. אפשר לגשת למסמך הזה בתמונת המצב שמוחזרת לפונקציה.

הפניה למסמך כוללת שיטות כמו 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

  • onDocumentCreatedWithAuthContext
  • onDocumentWrittenWithAuthContext
  • onDocumentDeletedWithAuthContext
  • onDocumentUpdatedWithAuthContext

Python

  • on_document_created_with_auth_context
  • on_document_updated_with_auth_context
  • on_document_deleted_with_auth_context
  • on_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 להפעיל בסביבה מהימנה. הם מורשים כחשבון שירות בפרויקט שלכם, ואתם יכולים לבצע קריאות וכתיבות באמצעות Firebase Admin SDK:

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({
      # ...
  })

מגבלות

  • אנחנו לא מתחייבים לגבי הזמנות. שינויים מהירים יכולים להפעיל קריאות לפונקציות בסדר לא צפוי.
  • האירועים מועברים לפחות פעם אחת, אבל אירוע יחיד עשוי להוביל להפעלות מרובות של פונקציות. מומלץ להימנע מהסתמכות על מנגנונים של 'פעם אחת בדיוק' ולכתוב פונקציות אידמפוטנטיות.
  • טריגר משויך למסד נתונים אחד. אי אפשר ליצור טריגר שתואם לכמה מסדי נתונים.
  • מחיקה של מסד נתונים לא מוחקת באופן אוטומטי טריגרים שקשורים למסד הנתונים הזה. הטריגר מפסיק לשלוח אירועים אבל ממשיך להתקיים עד שמוחקים אותו.