Étendre Firestore avec Cloud Functions
Avec Cloud Functions, vous pouvez déployer du code Node.js pour gérer les événements déclenchés lorsque votre base de données Firestore est modifiée. Ce service vous permet d'ajouter facilement des fonctionnalités côté serveur à votre application, sans avoir à gérer vos propres serveurs.
Pour obtenir des exemples de cas d'utilisation, consultez la page Que puis-je faire avec Cloud Functions ? ou le dépôt Extraits de fonctions sur GitHub.
Déclencheurs de fonction Firestore
Le SDK Cloud Functions for Firebase exporte un objet functions.firestore
qui vous permet de créer des gestionnaires associés à des événements Firestore spécifiques.
Type d'événement | Déclencheur |
---|---|
onCreate |
Déclenché lorsqu'un document est écrit pour la première fois. |
onUpdate |
Déclenché lorsqu'un document existe déjà et qu'une valeur y a été modifiée. |
onDelete |
Déclenché lorsqu'un document contenant des données est supprimé. |
onWrite |
Déclenché lorsque onCreate , onUpdate ou onDelete est déclenché. |
Si vous n'avez pas encore activé de projet dans Cloud Functions for Firebase, consultez la page Premiers pas : écrire et déployer vos premières fonctions pour en configurer un.
Écrire des fonctions déclenchées par Firestore
Définir un déclencheur de fonction
Pour définir un déclencheur Firestore, spécifiez un chemin de document et un type d'événement :
Node.js
const functions = require('firebase-functions');
exports.myFunction = functions.firestore
.document('my-collection/{docId}')
.onWrite((change, context) => { /* ... */ });
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
// Listen for any change on document `marie` in collection `users` exports.myFunctionName = functions.firestore .document('users/marie').onWrite((change, context) => { // ... Your code here });
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
// Listen for changes in all documents in the 'users' collection exports.useWildcard = functions.firestore .document('users/{userId}') .onWrite((change, context) => { // If we set `/users/marie` to {name: "Marie"} then // context.params.userId == "marie" // ... and ... // change.after.data() == {name: "Marie"} });
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 de la collection 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 context.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
// Listen for changes in all documents in the 'users' collection and all subcollections exports.useMultipleWildcards = functions.firestore .document('users/{userId}/{messageCollectionId}/{messageId}') .onWrite((change, context) => { // If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then // context.params.userId == "marie"; // context.params.messageCollectionId == "incoming_messages"; // context.params.messageId == "134"; // ... and ... // change.after.data() == {body: "Hello"} });
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 à l'aide d'un gestionnaire onCreate()
associé à un caractère générique.
Cet exemple de fonction appelle createUser
chaque fois qu'un nouveau profil utilisateur est ajouté :
Node.js
exports.createUser = functions.firestore .document('users/{userId}') .onCreate((snap, context) => { // Get an object representing the document // e.g. {'name': 'Marie', 'age': 66} const newValue = snap.data(); // access a particular field as you would any JS property const name = newValue.name; // perform desired operations ... });
Déclencher une fonction lorsqu'un document est mis à jour
Vous pouvez également déclencher une fonction lorsqu'un document est mis à jour à l'aide de la fonction onUpdate()
associée à un caractère générique. Cet exemple de fonction appelle updateUser
si un utilisateur modifie son profil :
Node.js
exports.updateUser = functions.firestore .document('users/{userId}') .onUpdate((change, context) => { // Get an object representing the document // e.g. {'name': 'Marie', 'age': 66} const newValue = change.after.data(); // ...or the previous value before this update const previousValue = change.before.data(); // access a particular field as you would any JS property const name = newValue.name; // perform desired operations ... });
Déclencher une fonction lorsqu'un document est supprimé
Vous pouvez également déclencher une fonction lorsqu'un document est supprimé à l'aide de la fonction onDelete()
associée à un caractère générique. Cet exemple de fonction appelle deleteUser
lorsqu'un utilisateur supprime son profil utilisateur :
Node.js
exports.deleteUser = functions.firestore .document('users/{userID}') .onDelete((snap, context) => { // Get an object representing the document prior to deletion // e.g. {'name': 'Marie', 'age': 66} const deletedValue = snap.data(); // perform desired operations ... });
Déclencher une fonction pour toute modification apportée à un document
Si le type d'événement déclenché n'a pas d'importance pour vous, vous pouvez écouter toutes les modifications d'un document Firestore à l'aide de la fonction onWrite()
associée à un caractère générique. Cet exemple de fonction appelle modifyUser
si un utilisateur est créé, mis à jour ou supprimé :
Node.js
exports.modifyUser = functions.firestore .document('users/{userID}') .onWrite((change, context) => { // Get an object with the current document value. // If the document does not exist, it has been deleted. const document = change.after.exists ? change.after.data() : null; // Get an object with the previous document value (for update or delete) const oldDocument = change.before.data(); // perform desired 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 change.before.data()
, qui contient l'instantané du document avant sa mise à jour.
De même, change.after.data()
contient l'état de l'instantané du document après sa mise à jour.
Node.js
exports.updateUser2 = functions.firestore .document('users/{userId}') .onUpdate((change, context) => { // Get an object representing the current document const newValue = change.after.data(); // ...or the previous value before this update const previousValue = change.before.data(); });
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 = snap.data().age; const name = snap.data()['name']; // Fetch data using built in accessor const experience = snap.get('experience');
Écrire des données
Chaque appel de fonction est associé à un document spécifique de votre base de données Firestore. La propriété ref
de l'instantané renvoyé à votre fonction vous permet d'accéder à ce document en tant que DocumentReference
.
DocumentReference
provient du SDK Node.js Firestore et inclut des méthodes telles que update()
, set()
et remove()
, qui vous permettent de modifier facilement le document ayant déclenché la fonction.
Node.js
// Listen for updates to any `user` document. exports.countNameChanges = functions.firestore .document('users/{userId}') .onUpdate((change, context) => { // Retrieve the current and previous value const data = change.after.data(); const previousData = change.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 change.after.ref.set({ name_change_count: count + 1 }, {merge: true}); });
Données situées à l'extérieur de l'événement déclencheur
Cloud Functions permet d'exécuter des fonctions dans un environnement de confiance, ce qui signifie qu'elles sont autorisées en tant que compte de service dans votre projet. Vous pouvez accéder aux données en lecture et en écriture à l'aide du SDK Firebase Admin :
Node.js
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.writeToFirestore = functions.firestore
.document('some/doc')
.onWrite((change, context) => {
db.doc('some/otherdoc').set({ ... });
});
Limites
Notez les limites suivantes concernant les déclencheurs Firestore pour Cloud Run Functions :
- Cloud Run Functions (1re génération) nécessite une base de données "(default)" existante en mode natif Firestore. La solution n'est pas compatible avec les bases de données nommées Firestore ni avec le mode Datastore. Veuillez utiliser Cloud Run Functions (2nd gen) pour configurer des événements dans ce cas.
- 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.
- Firestore en mode Datastore nécessite Cloud Run Functions (2nd gen). Cloud Run Functions (1re génération) n'est pas compatible avec le mode Datastore.
- Un déclencheur est associé à une seule base de données. Vous ne pouvez pas créer un déclencheur qui correspond à 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 Run Functions (1re génération).
- Les événements non distribués en raison de la taille de la requête sont consignés dans les journaux de plate-forme et sont comptabilisés dans l'utilisation des journaux du projet.
- Vous trouverez ces journaux dans l'explorateur de journaux avec le message "Event cannot deliver to Cloud function due to size exceeding the limit for 1st gen..." (l'événement ne peut pas être distribué à la fonction Cloud, car sa taille dépasse la limite pour la 1re génération...) de gravité
error
. Vous trouverez le nom de la fonction dans le champfunctionName
. Si le champreceiveTimestamp
date de moins d'une heure, 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 le code temporel. - Pour éviter une telle cadence, vous pouvez :
- Migrer et passer à Cloud Run Functions (2nd gen)
- Réduire la taille du document
- Supprimer les fonctions Cloud Run Functions en question
- Vous pouvez désactiver la journalisation proprement dite à l'aide d'exclusions, mais notez que les événements mis en cause ne seront toujours pas distribués.