Aktywatory bazy danych czasu rzeczywistego


Dzięki Cloud Functions możesz obsługiwać zdarzenia w Firebase Realtime Database bez konieczności aktualizowania kodu klienta. Cloud Functions umożliwia wykonywanie operacji Realtime Database z pełnymi uprawnieniami administracyjnymi i zapewnia, że każda zmiana w Realtime Database jest przetwarzana osobno. Zmiany w Firebase Realtime Database możesz wprowadzać za pomocą interfejsu DataSnapshot lub pakietu Admin SDK.

W typowym cyklu życia funkcja Firebase Realtime Database wykonuje te czynności:

  1. Czeka na zmiany w konkretnej lokalizacji Realtime Database.
  2. Aktywuje się, gdy nastąpi zdarzenie, i wykonuje swoje zadania (patrz Co mogę zrobić za pomocą Cloud Functions? (przykłady zastosowań).
  3. Otrzymuje obiekt danych zawierający zrzut danych przechowywanych w określonym dokumencie.

Aktywowanie funkcji Realtime Database

tworzyć nowe funkcje dla zdarzeń Realtime Database za pomocą functions.database. Aby kontrolować, kiedy funkcja się aktywuje, określ jeden z modułów obsługi zdarzeń i określ ścieżkę Realtime Database, w której będzie nasłuchiwać zdarzeń.

Konfigurowanie modułu obsługi zdarzenia

Funkcje umożliwiają obsługę zdarzeń Realtime Database na 2 poziomach szczegółowości: możesz nasłuchiwać tylko zdarzeń tworzenia, aktualizowania lub usuwania albo dowolnych zmian ścieżki. Cloud Functions obsługuje te moduły obsługi zdarzeń w przypadku Realtime Database:

  • onWrite(), który uruchamia się, gdy dane są tworzone, aktualizowane lub usuwane w Realtime Database.
  • onCreate(), który jest wyzwalany po utworzeniu nowych danych w usłudze Realtime Database.
  • onUpdate(), które uruchamia się, gdy dane są aktualizowane w: Realtime Database .
  • onDelete(), który uruchamia się po usunięciu danych z tabeli Realtime Database.

Podaj instancję i ścieżkę

Aby kontrolować, kiedy i gdzie Twoja funkcja ma się aktywować, wywołaj metodę ref(path) w celu określenia ścieżki i opcjonalnie wskaż instancję Realtime Database z instance('INSTANCE_NAME'). Jeśli nie określisz instancji, funkcja zostanie wdrożona do domyślnej instancji Realtime Database w projekcie Firebase. Przykład:

  • Domyślna instancja Realtime Database: functions.database.ref('/foo/bar')
  • instancja o nazwie „my-app-db-2”: functions.database.instance('my-app-db-2').ref('/foo/bar')

Te metody kierują funkcję do obsługi zapisu w określonym miejscu w obiekcie Realtime Database. Specyfikacje ścieżki pasują do wszystkich zapisów, które dotykają ścieżki, w tym zapisów wykonywanych pod nią. Jeśli ścieżkę funkcji ustawisz jako /foo/bar, funkcja będzie dopasowywać zdarzenia w obu tych lokalizacjach:

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

W obu przypadkach Firebase interpretuje, że zdarzenie występuje w czasie /foo/bar, a dane zdarzenia obejmują stare i nowe dane w czasie /foo/bar. Jeśli dane zdarzenia mogą być duże, rozważ użycie wielu funkcji na głębszych ścieżkach zamiast jednej funkcji w pobliżu katalogu głównego bazy danych. Aby uzyskać najlepszą wydajność, żądaj danych tylko na możliwie najgłębszym poziomie.

Możesz określić element ścieżki jako symbol wieloznaczny, otaczając go nawiasami klamrowymi. ref('foo/{bar}') pasuje do dowolnego elementu podrzędnego elementu /foo. Wartości tych komponentów ścieżki symbolu wieloznacznego są dostępne w obiekcie EventContext.params funkcji. W tym przykładzie wartość jest dostępna jako context.params.bar.

Ścieżki z symbolami wieloznacznymi mogą pasować do wielu zdarzeń z jednego zapisu. Wstawka

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

pasuje do ścieżki "/foo/{bar}" dwukrotnie: raz z "hello": "world" i ponownie z "firebase": "functions".

Obsługa danych zdarzenia

Podczas obsługi zdarzenia Realtime Database zwracany jest obiekt danych DataSnapshot. W przypadku zdarzeń onWrite lub onUpdate pierwszy parametr to obiekt Change zawierający 2 migawki, które reprezentują stan danych przed i po wywołaniu zdarzenia. W przypadku zdarzeń onCreate i onDelete zwracany obiekt danych to zrzut danych utworzonych lub usuniętych.

W tym przykładzie funkcja pobiera zrzut określonych danych w ścieżce, zamienia ciąg znaków w tym miejscu na wielkie litery i zapisuje zmodyfikowany ciąg znaków w bazie danych:

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

Uzyskiwanie dostępu do informacji uwierzytelniających użytkowników

Z poziomu EventContext.authEventContext.authType możesz uzyskać informacje o użytkowniku, w tym jego uprawnienia, które spowodowały uruchomienie funkcji. Może to być przydatne do egzekwowania zasad bezpieczeństwa, ponieważ pozwala funkcji wykonywać różne operacje w zależności od poziomu uprawnień użytkownika:

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

Możesz też wykorzystać informacje uwierzytelniające użytkownika do „podszywania się” pod użytkownika i wykonywania operacji zapisu w jego imieniu. Aby uniknąć problemów z jednoczestronnością, usuń instancję aplikacji w ten sposób:

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

Odczytywanie poprzedniej wartości

Obiekt Change ma właściwość before, która umożliwia sprawdzenie, co zostało zapisane w obiekcie Realtime Database przed zdarzeniem. Właściwość before zwraca wartość DataSnapshot, w której wszystkie metody (np. val()exists()) odwołują się do poprzedniej wartości. Nową wartość możesz odczytać ponownie, używając pierwotnej właściwości DataSnapshot lub odczytując właściwość after. Ta właściwość w przypadku dowolnego Change to kolejna DataSnapshot, która reprezentuje stan danych po wystąpieniu zdarzenia.

Na przykład przy użyciu właściwości before możesz mieć pewność, że podczas tworzenia funkcja pisze tylko wielkie litery w tekście:

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