Rozszerzanie bazy danych czasu rzeczywistego za pomocą Cloud Functions


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 ramach typowego 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 ma się aktywować, określ jeden z modułów obsługi zdarzeń i ścieżkę Realtime Database, na której będzie ona 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óre jest wywoływane, gdy w tabeli Realtime Database tworzone, aktualizowane lub usuwane są dane.
  • onCreate(), która jest uruchaniana, gdy w tabeli Realtime Database są tworzone nowe dane.
  • onUpdate(), które jest wywoływane, gdy dane są aktualizowane w tabeli Realtime Database .
  • onDelete(), który uruchamia się po usunięciu danych z tabeli Realtime Database .

Określ instancję i ścieżkę

Aby określić, kiedy i gdzie ma być wywoływana funkcja, użyj wywołania ref(path), aby podać ścieżkę, i opcjonalnie użyj wywołania Realtime Database, aby podać instancję Realtime Database.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 „mój-plik-db-2”: functions.database.instance('my-app-db-2').ref('/foo/bar')

Te metody kierują funkcję do obsługi zapisu na określonej ścieżce w instancji Realtime Database. Specyfikacje ścieżek pasują do wszystkich operacji zapisu, które dotyczą ścieżki, w tym operacji zapisu, które występują w dowolnym miejscu poniżej. 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 symboli wieloznacznych 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 przedstawiające 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);
    });

Dostęp do informacji o uwierzytelnianiu użytkowników

Z poziomu EventContext.authEventContext.authType możesz uzyskać informacje o użytkowniku, w tym jego uprawnienia, które wywołały funkcję. 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, aby „udawać” tego użytkownika i wykonywać operacje 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 za pomocą właściwości before możesz się upewnić, że funkcja będzie zamieniać tekst na wielkie litery tylko przy pierwszym utworzeniu:

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