Cloud Firestore mit Cloud Functions (2. Generation) erweitern

Mit Cloud Functions können Sie Code bereitstellen, um Ereignisse zu verarbeiten, die durch Änderungen in Ihrer Cloud Firestore-Datenbank ausgelöst werden. Auf diese Weise können Sie problemlos serverseitige Funktionen zu Ihrer App hinzufügen, ohne Ihre eigenen Server zu betreiben.

Cloud Functions (2nd gen)

Dank Cloud Run und Eventarc bietet Ihnen Cloud Functions for Firebase (2. Generation) eine leistungsfähigere Infrastruktur, erweiterte Kontrolle über Leistung und Skalierbarkeit sowie mehr Kontrolle über die Laufzeit von Funktionen. Weitere Informationen zur 2. Generation finden Sie unter Cloud Functions for Firebase (2. Generation). Weitere Informationen zur 1. Generation finden Sie unter Cloud Firestore mit Cloud Functions erweitern.

Die Cloud Firestore-Funktion wird ausgelöst

Das Cloud Functions for Firebase SDK exportiert die folgenden Cloud Firestore-Ereignistrigger, damit Sie Handler erstellen können, die mit bestimmten Cloud Firestore-Ereignissen verknüpft sind:

Node.js

Ereignistyp Trigger
onDocumentCreated Wird ausgelöst, wenn ein Dokument zum ersten Mal beschrieben wird.
onDocumentUpdated Wird ausgelöst, wenn ein Dokument bereits existiert und sich ein Wert geändert hat.
onDocumentDeleted Wird ausgelöst, wenn ein Dokument gelöscht wird.
onDocumentWritten Wird ausgelöst, wenn onDocumentCreated, onDocumentUpdated oder onDocumentDeleted ausgelöst wird.
onDocumentCreatedWithAuthContext onDocumentCreated mit zusätzlichen Authentifizierungsinformationen
onDocumentWrittenWithAuthContext onDocumentWritten mit zusätzlichen Authentifizierungsinformationen
onDocumentDeletedWithAuthContext onDocumentDeleted mit zusätzlichen Authentifizierungsinformationen
onDocumentUpdatedWithAuthContext onDocumentUpdated mit zusätzlichen Authentifizierungsinformationen

Python (Vorabversion)

Ereignistyp Trigger
on_document_created Wird ausgelöst, wenn ein Dokument zum ersten Mal beschrieben wird.
on_document_updated Wird ausgelöst, wenn ein Dokument bereits existiert und sich ein Wert geändert hat.
on_document_deleted Wird ausgelöst, wenn ein Dokument gelöscht wird.
on_document_written Wird ausgelöst, wenn on_document_created, on_document_updated oder on_document_deleted ausgelöst wird.
on_document_created_with_auth_context on_document_created mit zusätzlichen Authentifizierungsinformationen
on_document_updated_with_auth_context on_document_updated mit zusätzlichen Authentifizierungsinformationen
on_document_deleted_with_auth_context on_document_deleted mit zusätzlichen Authentifizierungsinformationen
on_document_written_with_auth_context on_document_written mit zusätzlichen Authentifizierungsinformationen

Cloud Firestore-Ereignisse werden nur bei Dokumentänderungen ausgelöst. Eine Aktualisierung eines Cloud Firestore-Dokuments, bei der die Daten unverändert bleiben (ein No-O-Schreibvorgang), führt nicht zu einem Aktualisierungs- oder Schreibereignis. Es ist nicht möglich, bestimmten Feldern Ereignisse hinzuzufügen.

Wenn Sie noch kein Projekt für Cloud Functions for Firebase aktiviert haben, lesen Sie den Artikel Erste Schritte mit Cloud Functions for Firebase (2. Generation), um Ihr Cloud Functions for Firebase-Projekt zu konfigurieren und einzurichten.

Schreiben von durch Cloud Firestore ausgelösten Funktionen

Funktions-Trigger definieren

Geben Sie einen Dokumentpfad und einen Ereignistyp an, um einen Cloud Firestore-Trigger zu definieren:

Node.js

import {
  onDocumentWritten,
  onDocumentCreated,
  onDocumentUpdated,
  onDocumentDeleted,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("my-collection/{docId}", (event) => {
   /* ... */ 
});

Python (Vorabversion)

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:

Dokumentpfade können entweder auf ein bestimmtes Dokument oder ein Platzhaltermuster verweisen.

Einzelnes Dokument angeben

Wenn Sie ein Ereignis für beliebige Änderungen an einem bestimmten Dokument auslösen möchten, können Sie die folgende Funktion verwenden.

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("users/marie", (event) => {
  // Your code here
});

Python (Vorabversion)

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:

Mithilfe von Platzhaltern eine Gruppe von Dokumenten angeben

Wenn Sie einen Trigger an eine Gruppe von Dokumenten anhängen möchten, z. B. an ein Dokument in einer bestimmten Sammlung, verwenden Sie {wildcard} anstelle der Dokument-ID:

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "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 (Vorabversion)

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

Wenn in diesem Beispiel ein Feld in einem Dokument im Verzeichnis users geändert wird, entspricht es einem Platzhalter namens userId.

Wenn ein Dokument in users untergeordnete Sammlungen hat und ein Feld in einem Dokument dieser Sammlungen geändert wird, wird der Platzhalter userId nicht ausgelöst.

Platzhalterübereinstimmungen werden aus dem Dokumentpfad extrahiert und in event.params gespeichert. Sie können beliebig viele Platzhalter festlegen, um explizite Sammlungs- oder Dokument-IDs zu ersetzen. Beispiel:

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "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 (Vorabversion)

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"}

Der Trigger muss immer auf ein Dokument verweisen, auch wenn Sie einen Platzhalter verwenden. users/{userId}/{messageCollectionId} ist beispielsweise ungültig, da {messageCollectionId} eine Sammlung ist. users/{userId}/{messageCollectionId}/{messageId} ist jedoch gültig, da {messageId} immer auf ein Dokument verweist.

Ereignis-Trigger

Beim Erstellen eines neuen Dokuments eine Funktion auslösen

Sie können festlegen, dass eine Funktion jedes Mal ausgelöst wird, wenn ein neues Dokument in einer Sammlung erstellt wird. Diese Beispielfunktion wird jedes Mal ausgelöst, wenn ein neues Nutzerprofil hinzugefügt wird:

Node.js

import {
  onDocumentCreated,
  Change,
  FirestoreEvent
} from "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 ...
});

Verwenden Sie onDocumentCreatedWithAuthContext, um weitere Informationen zur Authentifizierung zu erhalten.

Python (Vorabversion)

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

Funktion auslösen, wenn ein Dokument aktualisiert wird

Sie können auch festlegen, dass eine Funktion ausgelöst wird, wenn ein Dokument aktualisiert wird. Diese Beispielfunktion wird ausgelöst, wenn ein Nutzer sein Profil ändert:

Node.js

import {
  onDocumentUpdated,
  Change,
  FirestoreEvent
} from "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 ...
});

Verwenden Sie onDocumentUpdatedWithAuthContext, um weitere Informationen zur Authentifizierung zu erhalten.

Python (Vorabversion)

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

Funktion auslösen, wenn ein Dokument gelöscht wird

Sie können eine Funktion auch auslösen, wenn ein Dokument gelöscht wird. Diese Beispielfunktion wird ausgelöst, wenn ein Nutzer sein Nutzerprofil löscht:

Node.js

import {
  onDocumentDeleted,
  Change,
  FirestoreEvent
} from "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 ...
});

Verwenden Sie onDocumentDeletedWithAuthContext, um weitere Informationen zur Authentifizierung zu erhalten.

Python (Vorabversion)

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

Funktion für alle Änderungen an einem Dokument auslösen

Wenn es für Sie keine Rolle spielt, welche Art von Ereignis ausgelöst wird, können Sie alle Änderungen in einem Cloud Firestore-Dokument mithilfe des Ereignistrigger „Dokument erstellt“ überwachen. Diese Beispielfunktion wird ausgelöst, wenn ein Nutzer erstellt, aktualisiert oder gelöscht wird:

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "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 ...
});

Verwenden Sie onDocumentWrittenWithAuthContext, um weitere Informationen zur Authentifizierung zu erhalten.

Python (Vorabversion)

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

Daten lesen und schreiben

Wenn eine Funktion ausgelöst wird, stellt sie einen Snapshot der Daten bereit, die sich auf das Ereignis beziehen. Sie können diesen Snapshot verwenden, um das Dokument zu lesen oder zu schreiben, das das Ereignis ausgelöst hat, oder das Firebase Admin SDK verwenden, um auf andere Teile Ihrer Datenbank zuzugreifen.

Ereignisdaten

Daten lesen

Wenn eine Funktion ausgelöst wird, möchten Sie möglicherweise Daten aus einem Dokument abrufen, das aktualisiert wurde, oder die Daten vor der Aktualisierung abrufen. Sie können die vorherigen Daten mithilfe von event.data.before abrufen, das den Dokument-Snapshot vor der Aktualisierung enthält. Entsprechend enthält event.data.after den Status des Dokument-Snapshots nach der Aktualisierung.

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 (Vorabversion)

@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()

Sie können auf Attribute wie in jedem anderen Objekt zugreifen. Alternativ können Sie die Funktion get für den Zugriff auf bestimmte Felder verwenden:

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 (Vorabversion)

# 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"]

Daten schreiben

Jeder Funktionsaufruf ist einem bestimmten Dokument in Ihrer Cloud Firestore-Datenbank zugeordnet. Sie können auf dieses Dokument in dem Snapshot zugreifen, der an Ihre Funktion zurückgegeben wird.

Die Dokumentreferenz enthält Methoden wie update(), set() und remove(), damit Sie das Dokument ändern können, durch das die Funktion ausgelöst wurde.

Node.js

import { onDocumentUpdated } from "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 data.after.ref.set({
    name_change_count: count + 1
  }, {merge: true});

});

Python (Vorabversion)

@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})

Auf Informationen zur Nutzerauthentifizierung zugreifen

Wenn Sie einen der folgenden Ereignistypen verwenden, können Sie auf Informationen zur Nutzerauthentifizierung zum Hauptkonto zugreifen, das das Ereignis ausgelöst hat. Diese Informationen gelten zusätzlich zu den im Basisereignis zurückgegebenen Informationen.

Node.js

  • onDocumentCreatedWithAuthContext
  • onDocumentWrittenWithAuthContext
  • onDocumentDeletedWithAuthContext
  • onDocumentUpdatedWithAuthContext

Python (Vorabversion)

  • on_document_created_with_auth_context
  • on_document_updated_with_auth_context
  • on_document_deleted_with_auth_context
  • on_document_written_with_auth_context

Informationen zu den im Kontext der Authentifizierung verfügbaren Daten finden Sie unter Authentifizierungskontext. Das folgende Beispiel zeigt, wie Authentifizierungsinformationen abgerufen werden:

Node.js

import { onDocumentWrittenWithAuthContext } from "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 (Vorabversion)

@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

Daten außerhalb des Triggerereignisses

Cloud Functions-Funktionen werden in einer vertrauenswürdigen Umgebung ausgeführt. Sie sind als Dienstkonto für Ihr Projekt autorisiert und Sie können mit dem Firebase Admin SDK Lese- und Schreibvorgänge ausführen:

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 (Vorabversion)

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

Beschränkungen

Beachten Sie die folgenden Einschränkungen für Cloud Firestore-Trigger für Cloud Functions:

  • Die Reihenfolge ist nicht garantiert. Schnelle Änderungen können Funktionsaufrufe in einer unvorhergesehenen Reihenfolge auslösen.
  • Ereignisse werden mindestens einmal übergeben. Ein einzelnes Ereignis kann aber zu mehreren Funktionsaufrufen führen. Vermeiden Sie die Abhängigkeit von genau einmal vorkommenden Verfahren und schreiben Sie idempotente Funktionen.
  • Cloud Firestore im Datastore-Modus erfordert Cloud Functions (2. Generation). Cloud Functions (1st Gen) unterstützt den Datastore-Modus nicht.
  • Cloud Functions (1. Generation) funktioniert nur mit der „(Standard)“-Datenbank und unterstützt keine benannten Cloud Firestore-Datenbanken. Verwenden Sie Cloud Functions (2. Generation), um Ereignisse für benannte Datenbanken zu konfigurieren.
  • Ein Trigger ist einer einzelnen Datenbank zugeordnet. Sie können keinen Trigger erstellen, der mit mehreren Datenbanken übereinstimmt.
  • Durch das Löschen einer Datenbank werden die Trigger für diese Datenbank nicht automatisch gelöscht. Der Trigger sendet keine Ereignisse mehr, bleibt aber bestehen, bis Sie den Trigger löschen.
  • Wenn ein übereinstimmendes Ereignis die maximale Anfragegröße überschreitet, wird das Ereignis möglicherweise nicht an Cloud Functions (1. Generation) gesendet.
    • Ereignisse, die aufgrund der Größe der Anfrage nicht gesendet werden, werden in Plattformlogs protokolliert und auf die Lognutzung für das Projekt angerechnet.
    • Sie finden diese Logs im Log-Explorer mit der Meldung „Das Ereignis kann aufgrund von Überschreitung des Limits für die 1. Generation nicht an die Cloud Functions-Funktion gesendet werden...“ mit dem Schweregrad error. Sie finden den Funktionsnamen unter dem Feld functionName. Wenn das Feld receiveTimestamp noch innerhalb einer Stunde ab jetzt angezeigt wird, können Sie den tatsächlichen Ereignisinhalt ableiten. Lesen Sie dazu das betreffende Dokument mit einem Snapshot vor und nach dem Zeitstempel.
    • So können Sie solche Frequenzen vermeiden:
      • Zu Cloud Functions (2. Generation) migrieren und upgraden
      • Dokument verkleinern
      • Löschen Sie die betreffenden Cloud Functions-Funktionen
    • Sie können das Logging selbst mithilfe von Ausschlüssen deaktivieren. Beachten Sie jedoch, dass die betroffenen Ereignisse weiterhin nicht ausgeliefert werden.