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 uruchamianie operacji Realtime Database z pełnymi uprawnieniami administracyjnymi i dba o to, aby każda zmiana w Realtime Database była przetwarzana osobno. Możesz wprowadzić zmiany Firebase Realtime Database za pomocą DataSnapshot lub pakietu Admin SDK.

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

  1. Oczekiwanie na zmiany w określonej lokalizacji Realtime Database.
  2. Jest aktywowany, gdy zdarzenie występuje i wykonuje swoje zadania (zobacz Co mogę zrobić z funkcją Cloud Functions?) z przykładami zastosowań).
  3. Odbiera obiekt danych zawierający zrzut danych przechowywanych w określonym dokumencie.

Aktywowanie funkcji Realtime Database

Utwórz nowe funkcje dla zdarzeń Realtime Database za pomocą narzędzia 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ń.

Ustawianie modułu obsługi zdarzeń

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ń 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 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 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 w domyślnej instancji Realtime Database dla projektu Firebase. Na 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 zdarzeń są duże, rozważ użycie wielu funkcji na dalszych ścieżkach zamiast jednej funkcji w pobliżu poziomu głównego bazy danych. Aby uzyskać najlepszą skuteczność, proś o dane tylko na najgłębszym możliwym 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. Wstawienie

{
  "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

W przypadku obsługi zdarzenia Realtime Database zwracanym obiektem danych jest DataSnapshot. W przypadku zdarzeń onWrite lub onUpdate pierwszym parametrem jest obiekt Change zawierający 2 zrzuty obrazujące stan danych przed zdarzeniem wywołującym i po nim. W przypadku zdarzeń onCreate i onDelete zwracany obiekt danych to zrzut danych utworzonych lub usuniętych.

W tym przykładzie funkcja pobiera zrzut dla określonej ścieżki, zamienia ciąg znaków w tej lokalizacji na wielkie litery i zapisuje ten zmodyfikowany ciąg 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

W usługach EventContext.auth i EventContext.authType możesz uzyskać dostęp do informacji o użytkowniku, który uruchomił funkcję, w tym do jego uprawnień. Może to być przydatne do egzekwowania reguł zabezpieczeń, ponieważ umożliwia funkcji wykonywania różnych operacji 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 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 dowolnym elemencie Change to inna właściwość DataSnapshot reprezentująca 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);
    });