Realtime Database mit Cloud Functions erweitern


Mit Cloud Functions können Sie Ereignisse in der Firebase Realtime Database verarbeiten, ohne den Clientcode aktualisieren zu müssen. Mit Cloud Functions können Sie Realtime Database-Vorgänge mit vollen Administratorberechtigungen ausführen und dafür sorgen, dass jede Änderung an Realtime Database einzeln verarbeitet wird. Sie können Firebase Realtime Database-Änderungen über die DataSnapshot oder über das Admin SDK vornehmen.

In einem typischen Lebenszyklus führt eine Firebase Realtime Database-Funktion so vor:

  1. Sie wartet auf Änderungen an einem bestimmten Realtime Database-Speicherort.
  2. Sie wird ausgelöst, wenn ein Ereignis eintritt, und führt dessen Aufgaben aus (siehe Was kann ich mit Cloud Functions tun? für Beispiele für Anwendungsfälle).
  3. Sie empfängt ein Datenobjekt, das einen Snapshot der im angegebenen Dokument gespeicherten Daten enthält.

Realtime Database-Funktion auslösen

Mit functions.database können Sie neue Funktionen für Realtime Database-Ereignisse erstellen. Wenn Sie festlegen möchten, wann die Funktion ausgelöst wird, geben Sie einen der Ereignishandler und den Realtime Database-Pfad an, unter dem auf Ereignisse gewartet werden soll.

Event-Handler festlegen

Mit Cloud Functions können Sie Realtime Database-Ereignisse mit zwei Spezifitätsgraden verarbeiten: Sie können gezielt nur nach Erstellungs-, Aktualisierungs- oder Löschereignissen oder nach beliebigen Änderungen jeglicher Art an einem Pfad Ausschau halten. Cloud Functions unterstützt die folgenden Ereignishandler für Realtime Database:

  • onWrite(): wird ausgelöst, wenn Daten in Realtime Database erstellt, aktualisiert oder gelöscht werden.
  • onCreate(), der ausgelöst wird, wenn in Realtime Database neue Daten erstellt werden.
  • onUpdate(), der ausgelöst wird, wenn Daten in Realtime Database aktualisiert werden.
  • onDelete(), der ausgelöst wird, wenn Daten aus Realtime Database gelöscht werden .

Instanz und Pfad angeben

Wenn Sie festlegen möchten, wann und wo Ihre Funktion ausgelöst werden soll, rufen Sie ref(path) auf, um einen Pfad anzugeben, und optional eine Realtime Database-Instanz mit instance('INSTANCE_NAME'). Wenn Sie keine Instanz angeben, wird die Funktion in der Standard-Realtime Database-Instanz für das Firebase-Projekt bereitgestellt. Beispiel:

  • Standard-Realtime Database-Instanz: functions.database.ref('/foo/bar')
  • Instanz mit dem Namen „my-app-db-2“: functions.database.instance('my-app-db-2').ref('/foo/bar')

Diese Methoden weisen Ihre Funktion an, Schreibvorgänge in einem bestimmten Pfad innerhalb der Realtime Database-Instanz zu verarbeiten. Die Pfadspezifikationen gleichen alle Schreibvorgänge ab, die einen Pfad tangieren, einschließlich Schreibvorgängen, die an einem beliebigen untergeordneten Punkt auftreten. Wenn Sie den Pfad für die Funktion als /foo/bar festlegen, werden Ereignisse an diesen beiden Speicherorten abgeglichen:

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

In beiden Fällen geht Firebase davon aus, dass das Ereignis unter /foo/bar auftritt, und die Ereignisdaten umfassen sowohl die alten als auch die neuen Daten unter /foo/bar. Wenn die Ereignisdaten sehr umfangreich sein können, sollten Sie erwägen, mehrere Funktionen auf tieferen Pfadebenen anstatt lediglich einer Funktion im Bereich des Datenbankstamms zu verwenden. Für optimale Leistung sollten Sie Daten nur auf der untersten Ebene anfordern.

Sie können eine Pfadkomponente als Platzhalter angeben, indem Sie sie in geschweifte Klammern setzen. ref('foo/{bar}') entspricht allen untergeordneten Elementen von /foo. Die Werte dieser Platzhalterpfadkomponenten sind innerhalb des EventContext.params-Objekts Ihrer Funktion verfügbar. In diesem Beispiel ist der Wert als context.params.bar verfügbar.

Pfade mit Platzhaltern können mit mehreren Ereignissen aus einem einzigen Schreibvorgang übereinstimmen. Die Verwendung von

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

ergibt zwei Übereinstimmungen mit dem Pfad "/foo/{bar}": einmal "hello": "world" und einmal "firebase": "functions".

Ereignisdaten verarbeiten

Bei der Verarbeitung eines Realtime Database-Ereignisses wird ein DataSnapshot als Datenobjekt zurückgegeben. Bei onWrite- oder onUpdate-Ereignissen ist der erste Parameter ein Change-Objekt mit zwei Snapshots, die den Datenstatus vor und nach dem auslösenden Ereignis darstellen. Bei onCreate- und onDelete-Ereignissen ist das zurückgegebene Datenobjekt ein Snapshot der erstellten oder gelöschten Daten.

In diesem Beispiel ruft die Funktion den Snapshot für den angegebenen Pfad ab, konvertiert den String an dieser Stelle in Großbuchstaben und schreibt diesen geänderten String in die Datenbank:

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

Zugriff auf Informationen zur Nutzerauthentifizierung

Über EventContext.auth und EventContext.authType können Sie auf die Nutzerinformationen, einschließlich Berechtigungen, des Nutzers zugreifen, der eine Funktion ausgelöst hat. Das kann hilfreich sein, um Sicherheitsregeln durchzusetzen, sodass Ihre Funktion je nach Berechtigungsstufe des Nutzers unterschiedliche Vorgänge ausführen kann:

const functions = require('firebase-functions/v1');
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);
      }
    });

Außerdem können Sie Informationen zur Nutzerauthentifizierung nutzen, um sich als Nutzer auszugeben und im Namen des Nutzers Schreibvorgänge auszuführen. Achten Sie darauf, die Anwendungsinstanz wie unten gezeigt zu löschen, um Gleichzeitigkeitsprobleme zu vermeiden:

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

Vorherigen Wert lesen

Das Change-Objekt hat die Eigenschaft before, mit der Sie prüfen können, was vor dem Ereignis in Realtime Database gespeichert wurde. Das Attribut before gibt einen DataSnapshot-Wert zurück, in dem alle Methoden (z. B. val() und exists()) auf den vorherigen Wert verweisen. Sie können den neuen Wert noch einmal lesen, indem Sie entweder die ursprüngliche DataSnapshot verwenden oder das Attribut after lesen. Diese Property bei einer Change ist eine weitere DataSnapshot, die den Status der Daten nach dem Ereignis darstellt.

Mit dem Attribut before können Sie beispielsweise festlegen, dass die Funktion Text nur beim ersten Erstellen in Großbuchstaben schreibt:

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