Déclencheurs de base de données en temps réel


Avec Cloud Functions, vous pouvez gérer les événements dans la base de données en temps réel Firebase sans avoir besoin de mettre à jour le code client. Cloud Functions vous permet d'exécuter des opérations de base de données en temps réel avec des privilèges administratifs complets et garantit que chaque modification apportée à la base de données en temps réel est traitée individuellement. Vous pouvez apporter des modifications à la base de données en temps réel Firebase via DataSnapshot ou via le SDK Admin .

Dans un cycle de vie typique, une fonction Firebase Realtime Database effectue les opérations suivantes :

  1. Attend les modifications apportées à un emplacement particulier de la base de données en temps réel.
  2. Se déclenche lorsqu'un événement se produit et exécute ses tâches (voir Que puis-je faire avec Cloud Functions ? pour des exemples de cas d'utilisation).
  3. Reçoit un objet de données qui contient un instantané des données stockées dans le document spécifié.

Déclencher une fonction de base de données en temps réel

Créez de nouvelles fonctions pour les événements de base de données en temps réel avec functions.database . Pour contrôler le moment où la fonction se déclenche, spécifiez l'un des gestionnaires d'événements et spécifiez le chemin de la base de données en temps réel où il écoutera les événements.

Définir le gestionnaire d'événements

Les fonctions vous permettent de gérer les événements de base de données en temps réel à deux niveaux de spécificité : vous pouvez écouter spécifiquement uniquement les événements de création, de mise à jour ou de suppression, ou vous pouvez écouter toute modification de quelque nature que ce soit apportée à un chemin. Cloud Functions prend en charge ces gestionnaires d'événements pour Realtime Database :

  • onWrite() , qui se déclenche lorsque les données sont créées, mises à jour ou supprimées dans la base de données en temps réel.
  • onCreate() , qui se déclenche lorsque de nouvelles données sont créées dans la base de données en temps réel.
  • onUpdate() , qui se déclenche lorsque les données sont mises à jour dans Realtime Database .
  • onDelete() , qui se déclenche lorsque les données sont supprimées de Realtime Database .

Spécifiez l'instance et le chemin

Pour contrôler quand et où votre fonction doit se déclencher, appelez ref(path) pour spécifier un chemin et éventuellement spécifiez une instance de base de données en temps réel avec instance('INSTANCE_NAME') . Si vous ne spécifiez pas d'instance, la fonction se déploie sur l'instance de base de données en temps réel par défaut pour le projet Firebase. Par exemple :

  • Instance de base de données en temps réel par défaut : functions.database.ref('/foo/bar')
  • Instance nommée « my-app-db-2 » : functions.database.instance('my-app-db-2').ref('/foo/bar')

Ces méthodes demandent à votre fonction de gérer les écritures sur un certain chemin dans l'instance de base de données en temps réel. Les spécifications de chemin correspondent à toutes les écritures qui touchent un chemin, y compris les écritures qui se produisent n'importe où en dessous de celui-ci. Si vous définissez le chemin de votre fonction comme /foo/bar , il correspond aux événements à ces deux emplacements :

 /foo/bar
 /foo/bar/baz/really/deep/path

Dans les deux cas, Firebase interprète que l'événement se produit à /foo/bar et que les données de l'événement incluent les anciennes et les nouvelles données à /foo/bar . Si les données d'événement peuvent être volumineuses, envisagez d'utiliser plusieurs fonctions sur des chemins plus profonds au lieu d'une seule fonction près de la racine de votre base de données. Pour de meilleures performances, demandez uniquement des données au niveau le plus profond possible.

Vous pouvez spécifier un composant de chemin comme caractère générique en l'entourant d'accolades ; ref('foo/{bar}') correspond à n'importe quel enfant de /foo . Les valeurs de ces composants de chemin générique sont disponibles dans l'objet EventContext.params de votre fonction. Dans cet exemple, la valeur est disponible sous la forme context.params.bar .

Les chemins avec des caractères génériques peuvent correspondre à plusieurs événements d'une seule écriture. Un insert de

{
  "foo": {
    "hello": "world",
    "firebase": "functions"
  }
}

correspond au chemin "/foo/{bar}" deux fois : une fois avec "hello": "world" et encore avec "firebase": "functions" .

Gérer les données d'événement

Lors de la gestion d'un événement Realtime Database, l'objet de données renvoyé est un DataSnapshot . Pour les événements onWrite ou onUpdate , le premier paramètre est un objet Change qui contient deux instantanés qui représentent l'état des données avant et après l'événement déclencheur. Pour les événements onCreate et onDelete , l'objet de données renvoyé est un instantané des données créées ou supprimées.

Dans cet exemple, la fonction récupère l'instantané pour le chemin spécifié, convertit la chaîne à cet emplacement en majuscule et écrit cette chaîne modifiée dans la base de données :

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snapshot, context) => {
      // Grab the current value of what was written to the Realtime Database.
      const original = snapshot.val();
      functions.logger.log('Uppercasing', context.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing asynchronous tasks inside a Functions such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
      return snapshot.ref.parent.child('uppercase').set(uppercase);
    });

Accéder aux informations d'authentification des utilisateurs

À partir de EventContext.auth et EventContext.authType , vous pouvez accéder aux informations utilisateur, y compris les autorisations, pour l'utilisateur qui a déclenché une fonction. Cela peut être utile pour appliquer des règles de sécurité, permettant à votre fonction d'effectuer différentes opérations en fonction du niveau d'autorisations de l'utilisateur :

const functions = require('firebase-functions');
const admin = require('firebase-admin');

exports.simpleDbFunction = functions.database.ref('/path')
    .onCreate((snap, context) => {
      if (context.authType === 'ADMIN') {
        // do something
      } else if (context.authType === 'USER') {
        console.log(snap.val(), 'written by', context.auth.uid);
      }
    });

En outre, vous pouvez exploiter les informations d'authentification de l'utilisateur pour « usurper l'identité » d'un utilisateur et effectuer des opérations d'écriture au nom de l'utilisateur. Assurez-vous de supprimer l'instance d'application comme indiqué ci-dessous afin d'éviter les problèmes de concurrence :

exports.impersonateMakeUpperCase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snap, context) => {
      const appOptions = JSON.parse(process.env.FIREBASE_CONFIG);
      appOptions.databaseAuthVariableOverride = context.auth;
      const app = admin.initializeApp(appOptions, 'app');
      const uppercase = snap.val().toUpperCase();
      const ref = snap.ref.parent.child('uppercase');

      const deleteApp = () => app.delete().catch(() => null);

      return app.database().ref(ref).set(uppercase).then(res => {
        // Deleting the app is necessary for preventing concurrency leaks
        return deleteApp().then(() => res);
      }).catch(err => {
        return deleteApp().then(() => Promise.reject(err));
      });
    });

Lecture de la valeur précédente

L'objet Change possède une propriété before qui vous permet d'inspecter ce qui a été enregistré dans la base de données en temps réel avant l'événement. La propriété before renvoie un DataSnapshot où toutes les méthodes (par exemple, val() et exists() ) font référence à la valeur précédente. Vous pouvez relire la nouvelle valeur en utilisant le DataSnapshot d'origine ou en lisant la propriété after . Cette propriété sur tout Change est un autre DataSnapshot représentant l'état des données après l'événement.

Par exemple, la propriété before peut être utilisée pour garantir que la fonction ne met en majuscule que le texte lors de sa première création :

exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onWrite((change, context) => {
      // Only edit data when it is first created.
      if (change.before.exists()) {
        return null;
      }
      // Exit when the data is deleted.
      if (!change.after.exists()) {
        return null;
      }
      // Grab the current value of what was written to the Realtime Database.
      const original = change.after.val();
      console.log('Uppercasing', context.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing asynchronous tasks inside a Functions such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
      return change.after.ref.parent.child('uppercase').set(uppercase);
    });