Étendre Cloud Firestore avec Cloud Functions (2e génération)

Avec Cloud Functions, vous pouvez déployer du code pour gérer les événements déclenchés par les modifications apportées à votre base de données Cloud Firestore. Ce service vous permet d'ajouter facilement des fonctionnalités côté serveur à votre application, sans avoir à gérer vos propres serveurs.

Cloud Functions (2nd gen)

Basé sur Cloud Run et Eventarc, Cloud Functions for Firebase (2e génération) vous offre une infrastructure plus puissante, un contrôle avancé sur les performances et l'évolutivité, ainsi qu'un contrôle accru sur l'environnement d'exécution des fonctions. Pour en savoir plus sur la 2e génération, consultez la page Cloud Functions for Firebase (2nd gen). Pour en savoir plus sur la 1re génération, consultez la page Étendre Cloud Firestore avec Cloud Functions.

Déclencheurs de fonction Cloud Firestore

Le SDK Cloud Functions for Firebase exporte les déclencheurs d'événement Cloud Firestore suivants pour vous permettre de créer des gestionnaires liés à des événements Cloud Firestore spécifiques:

Node.js

Type d'événement Déclencheur
onDocumentCreated Déclenché lorsqu'un document est écrit pour la première fois.
onDocumentUpdated Déclenché lorsqu'un document existe déjà et qu'une valeur y a été modifiée.
onDocumentDeleted Déclenché lorsqu'un document est supprimé.
onDocumentWritten Déclenché lorsque onDocumentCreated, onDocumentUpdated ou onDocumentDeleted est déclenché.
onDocumentCreatedWithAuthContext onDocumentCreated avec des informations d'authentification supplémentaires
onDocumentWrittenWithAuthContext onDocumentWritten avec des informations d'authentification supplémentaires
onDocumentDeletedWithAuthContext onDocumentDeleted avec des informations d'authentification supplémentaires
onDocumentUpdatedWithAuthContext onDocumentUpdated avec des informations d'authentification supplémentaires

Python (preview)

Type d'événement Déclencheur
on_document_created Déclenché lorsqu'un document est écrit pour la première fois.
on_document_updated Déclenché lorsqu'un document existe déjà et qu'une valeur y a été modifiée.
on_document_deleted Déclenché lorsqu'un document est supprimé.
on_document_written Déclenché lorsque on_document_created, on_document_updated ou on_document_deleted est déclenché.
on_document_created_with_auth_context on_document_created avec des informations d'authentification supplémentaires
on_document_updated_with_auth_context on_document_updated avec des informations d'authentification supplémentaires
on_document_deleted_with_auth_context on_document_deleted avec des informations d'authentification supplémentaires
on_document_written_with_auth_context on_document_written avec des informations d'authentification supplémentaires

Les événements Cloud Firestore ne se déclenchent qu'en cas de modification du document. Une mise à jour d'un document Cloud Firestore dans laquelle les données restent inchangées (écriture no-op) ne génère pas d'événement de mise à jour ou d'écriture. Il n'est pas possible d'ajouter des événements à des champs spécifiques.

Si vous n'avez pas encore de projet activé pour Cloud Functions for Firebase, consultez la page Premiers pas avec Cloud Functions for Firebase (2e génération) pour configurer votre projet Cloud Functions for Firebase.

Écrire des fonctions déclenchées par Cloud Firestore

Définir un déclencheur de fonction

Pour définir un déclencheur Cloud Firestore, spécifiez un chemin de document et un type d'événement :

Node.js

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

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

Python (preview)

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:

Les chemins de document peuvent faire référence soit à un document spécifique, soit à un format contenant des caractères génériques.

Spécifier un document unique

Si vous souhaitez déclencher un événement chaque fois qu'une modification est constatée sur un document spécifique, vous pouvez utiliser la fonction suivante.

Node.js

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

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

Python (preview)

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:

Spécifier un groupe de documents à l'aide de caractères génériques

Si vous souhaitez associer un déclencheur à un groupe de documents, tel que l'ensemble des documents d'une collection spécifique, utilisez un caractère générique {wildcard} à la place de l'identifiant du document :

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

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

Dans cet exemple, lorsqu'un champ de n'importe quel document de la collection users est modifié, il correspond au caractère générique userId.

Si un document dans users comporte des sous-collections et qu'un champ de l'une d'entre elles est modifié, le caractère générique userId n'est pas déclenché.

Les correspondances de caractères génériques sont extraites du chemin du document et stockées dans le fichier event.params. Vous pouvez définir autant de caractères génériques que vous le souhaitez pour remplacer les identifiants explicites de collection ou de document, par exemple :

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

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

Votre déclencheur doit toujours pointer vers un document, même si vous utilisez un caractère générique. Par exemple, users/{userId}/{messageCollectionId} n'est pas valide, car {messageCollectionId} est une collection. Cependant, users/{userId}/{messageCollectionId}/{messageId} est valide, car {messageId} pointe toujours vers un document.

Déclencheurs d'événements

Déclencher une fonction lorsqu'un document est créé

Vous pouvez déclencher une fonction chaque fois qu'un document est créé dans une collection. Cet exemple de fonction se déclenche chaque fois qu'un profil utilisateur est ajouté:

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 ...
});

Pour obtenir des informations d'authentification supplémentaires, utilisez onDocumentCreatedWithAuthContext.

Python (preview)

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

Déclencher une fonction lorsqu'un document est mis à jour

Vous pouvez également déclencher une fonction lorsqu'un document est mis à jour. Cet exemple de fonction se déclenche si un utilisateur modifie son profil:

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 ...
});

Pour obtenir des informations d'authentification supplémentaires, utilisez onDocumentUpdatedWithAuthContext.

Python (preview)

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

Déclencher une fonction lorsqu'un document est supprimé

Vous pouvez également déclencher une fonction lorsqu'un document est supprimé. Cet exemple de fonction se déclenche lorsqu'un utilisateur supprime son profil utilisateur:

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 ...
});

Pour obtenir des informations d'authentification supplémentaires, utilisez onDocumentDeletedWithAuthContext.

Python (preview)

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

Déclencher une fonction pour toute modification apportée à un document

Si vous ne vous souciez pas du type d'événement déclenché, vous pouvez écouter toutes les modifications apportées à un document Cloud Firestore à l'aide du déclencheur d'événement "document write". Cet exemple de fonction se déclenche si un utilisateur est créé, mis à jour ou supprimé:

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 ...
});

Pour obtenir des informations d'authentification supplémentaires, utilisez onDocumentWrittenWithAuthContext.

Python (preview)

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

Lire et écrire des données

Lorsqu'une fonction est déclenchée, elle fournit un instantané des données associées à l'événement. Vous pouvez utiliser cet instantané pour lire ou écrire dans le document ayant déclenché l'événement, ou utiliser le SDK Firebase Admin pour accéder à d'autres parties de votre base de données.

Données d'événement

Lire les données

Lorsqu'une fonction est déclenchée, vous pouvez obtenir les données d'un document mis à jour ou récupérer la version antérieure à la mise à jour. Vous pouvez obtenir la version antérieure en utilisant event.data.before, qui contient l'instantané du document avant sa mise à jour. De même, event.data.after contient l'état de l'instantané du document après sa mise à jour.

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

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

Vous pouvez accéder aux propriétés comme vous le feriez pour n'importe quel autre objet. Vous pouvez également utiliser la fonction get pour accéder à des champs spécifiques :

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

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

Écrire des données

Chaque appel de fonction est associé à un document spécifique de votre base de données Cloud Firestore. Vous pouvez accéder à ce document dans l'instantané renvoyé à votre fonction.

La documentation de référence du document inclut des méthodes telles que update(), set() et remove() afin que vous puissiez modifier le document qui a déclenché la fonction.

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

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

Accéder aux informations d'authentification des utilisateurs

Si vous utilisez l'un des types d'événements suivants, vous pouvez accéder aux informations d'authentification des utilisateurs sur le compte principal qui a déclenché l'événement. Ces informations s'ajoutent aux informations renvoyées dans l'événement de base.

Node.js

  • onDocumentCreatedWithAuthContext
  • onDocumentWrittenWithAuthContext
  • onDocumentDeletedWithAuthContext
  • onDocumentUpdatedWithAuthContext

Python (preview)

  • on_document_created_with_auth_context
  • on_document_updated_with_auth_context
  • on_document_deleted_with_auth_context
  • on_document_written_with_auth_context

Pour en savoir plus sur les données disponibles dans le contexte d'authentification, consultez la section Contexte d'authentification. L'exemple suivant montre comment récupérer les informations d'authentification:

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

@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

Données situées à l'extérieur de l'événement déclencheur

Les fonctions Cloud s'exécutent dans un environnement de confiance. Ils sont autorisés en tant que compte de service sur votre projet, et vous pouvez effectuer des opérations de lecture et d'écriture à l'aide du SDK Admin 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 (preview)

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

Limites

Notez les limites suivantes relatives aux déclencheurs Cloud Firestore pour Cloud Functions:

  • L'ordre n'est pas garanti. Les modifications rapides peuvent déclencher des appels de fonctions dans un ordre inattendu.
  • Bien que les événements soient diffusés une fois au moins, un même événement peut produire plusieurs appels de fonction. Évitez de dépendre de procédés dits "exactement une fois" et écrivez des fonctions idempotentes.
  • Cloud Firestore en mode Datastore nécessite Cloud Functions (2e génération). Cloud Functions (1re génération) n'est pas compatible avec le mode Datastore.
  • Cloud Functions (1re génération) ne fonctionne qu'avec la base de données "(par défaut)" et n'est pas compatible avec les bases de données nommées Cloud Firestore. Veuillez configurer Cloud Functions (2nd gen) pour des bases de données nommées.
  • Un déclencheur est associé à une seule base de données. Vous ne pouvez pas créer de déclencheur correspondant à plusieurs bases de données.
  • La suppression d'une base de données ne supprime pas automatiquement les déclencheurs de cette base de données. Le déclencheur cesse de diffuser des événements, mais continue d'exister jusqu'à ce que vous le supprimiez.
  • Si un événement correspondant dépasse la taille maximale de requête, il risque de ne pas être distribué à Cloud Functions (1re génération).
    • Les événements non diffusés en raison de la taille de la requête sont consignés dans les journaux de la plate-forme et sont comptabilisés dans l'utilisation des journaux pour le projet.
    • Vous trouverez ces journaux dans l'explorateur de journaux avec le message "Impossible de distribuer l'événement à la fonction Cloud, car la taille dépasse la limite pour la 1re génération..." de gravité error. Vous trouverez le nom de la fonction dans le champ functionName. Si le champ receiveTimestamp se trouve toujours dans l'heure qui suit, vous pouvez déduire le contenu réel de l'événement en lisant le document en question avec un instantané avant et après l'horodatage.
    • Pour éviter une telle cadence, vous pouvez :
      • Migrer et passer à Cloud Functions (2e génération)
      • Réduire la taille du document
      • Supprimer les fonctions Cloud Functions en question
    • Vous pouvez désactiver la journalisation elle-même à l'aide d'exclusions, mais sachez que les événements incriminés ne seront toujours pas transmis.