Rozszerzanie Cloud Firestore za pomocą Cloud Functions

Za pomocą Cloud Functions możesz wdrażać kod Node.js do obsługi zdarzeń wywoływanych przez zmiany w bazie danych Cloud Firestore. Umożliwia to łatwe dodawanie do aplikacji funkcji po stronie serwera bez konieczności uruchamiania własnych serwerów.

Przykłady zastosowań znajdziesz w artykule Co mogę zrobić w Cloud Functions? lub w repozytorium GitHub Functions Samples.

Wyzwalacze funkcji Cloud Firestore

Pakiet SDK eksportuje obiekt Cloud Functions for Firebasefunctions.firestore, który umożliwia tworzenie procedur obsługi powiązanych z określonymi Cloud Firestore zdarzeniami.

Typ zdarzenia Aktywator
onCreate Wywoływane, gdy dokument jest zapisywany po raz pierwszy.
onUpdate Wywoływane, gdy dokument już istnieje i zmieni się jego wartość.
onDelete Wywoływane, gdy dokument z danymi zostanie usunięty.
onWrite Wywoływane, gdy zostanie wywołany element onCreate, onUpdate lub onDelete.

Jeśli nie masz jeszcze projektu z włączoną usługą Cloud Functions for Firebase, przeczytaj artykuł Wprowadzenie: pisanie i wdrażanie pierwszych funkcji, aby skonfigurować projekt Cloud Functions for Firebase.

Pisanie funkcji wywoływanych przez Cloud Firestore

Definiowanie wyzwalacza funkcji

Aby zdefiniować Cloud Firestore regułę, określ ścieżkę dokumentu i typ zdarzenia:

Node.js

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

exports.myFunction = functions.firestore
  .document('my-collection/{docId}')
  .onWrite((change, context) => { /* ... */ });

Ścieżki dokumentów mogą odwoływać się do konkretnego dokumentu lub wzorca z symbolami wieloznacznymi.

Określanie pojedynczego dokumentu

Jeśli chcesz wywołać zdarzenie przy każdej zmianie w określonym dokumencie, możesz użyć tej funkcji.

Node.js

// Listen for any change on document `marie` in collection `users`
exports.myFunctionName = functions.firestore
    .document('users/marie').onWrite((change, context) => {
      // ... Your code here
    });

Określanie grupy dokumentów za pomocą symboli wieloznacznych

Jeśli chcesz dołączyć wyzwalacz do grupy dokumentów, np. do dowolnego dokumentu w określonej kolekcji, użyj znaku {wildcard} zamiast identyfikatora dokumentu:

Node.js

// Listen for changes in all documents in the 'users' collection
exports.useWildcard = functions.firestore
    .document('users/{userId}')
    .onWrite((change, context) => {
      // If we set `/users/marie` to {name: "Marie"} then
      // context.params.userId == "marie"
      // ... and ...
      // change.after.data() == {name: "Marie"}
    });

W tym przykładzie, gdy jakiekolwiek pole w dowolnym dokumencie w users zostanie zmienione, będzie pasować do symbolu wieloznacznego o nazwie userId.

Jeśli dokument w users ma podzbiory, a pole w jednym z dokumentów podzbioru zostanie zmienione, symbol wieloznaczny userId nie zostanie wywołany.

Wzorce wieloznaczne są wyodrębniane ze ścieżki dokumentu i przechowywane w context.params. Możesz zdefiniować dowolną liczbę symboli wieloznacznych, aby zastąpić nimi np. identyfikatory kolekcji lub dokumentów:

Node.js

// Listen for changes in all documents in the 'users' collection and all subcollections
exports.useMultipleWildcards = functions.firestore
    .document('users/{userId}/{messageCollectionId}/{messageId}')
    .onWrite((change, context) => {
      // If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
      // context.params.userId == "marie";
      // context.params.messageCollectionId == "incoming_messages";
      // context.params.messageId == "134";
      // ... and ...
      // change.after.data() == {body: "Hello"}
    });

Aktywatory zdarzeń

Wywoływanie funkcji po utworzeniu nowego dokumentu

Możesz wywołać funkcję za każdym razem, gdy w kolekcji zostanie utworzony nowy dokument, używając modułu obsługi onCreate()symbolem wieloznacznym. Ta przykładowa funkcja wywołuje createUser za każdym razem, gdy dodawany jest nowy profil użytkownika:

Node.js

exports.createUser = functions.firestore
    .document('users/{userId}')
    .onCreate((snap, context) => {
      // Get an object representing the document
      // e.g. {'name': 'Marie', 'age': 66}
      const newValue = snap.data();

      // access a particular field as you would any JS property
      const name = newValue.name;

      // perform desired operations ...
    });

Uruchamianie funkcji po zaktualizowaniu dokumentu

Możesz też uruchomić funkcję, gdy dokument zostanie zaktualizowany, używając funkcji onUpdate()wieloznacznym symbolem. Ta przykładowa funkcja wywołuje updateUser, jeśli użytkownik zmieni swój profil:

Node.js

exports.updateUser = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
      // Get an object representing the document
      // e.g. {'name': 'Marie', 'age': 66}
      const newValue = change.after.data();

      // ...or the previous value before this update
      const previousValue = change.before.data();

      // access a particular field as you would any JS property
      const name = newValue.name;

      // perform desired operations ...
    });

Uruchamianie funkcji po usunięciu dokumentu

Możesz też uruchomić funkcję, gdy dokument zostanie usunięty, używając funkcji onDelete()symbolem wieloznacznym. W tym przykładzie funkcja wywołuje deleteUser, gdy użytkownik usuwa swój profil:

Node.js

exports.deleteUser = functions.firestore
    .document('users/{userID}')
    .onDelete((snap, context) => {
      // Get an object representing the document prior to deletion
      // e.g. {'name': 'Marie', 'age': 66}
      const deletedValue = snap.data();

      // perform desired operations ...
    });

Aktywowanie funkcji w przypadku wszystkich zmian w dokumencie

Jeśli nie zależy Ci na typie wywoływanego zdarzenia, możesz nasłuchiwać wszystkich zmian w dokumencie Cloud Firestore za pomocą funkcji onWrite() z symbolem wieloznacznym. Ta przykładowa funkcja wywołuje modifyUser, gdy użytkownik zostanie utworzony, zaktualizowany lub usunięty:

Node.js

exports.modifyUser = functions.firestore
    .document('users/{userID}')
    .onWrite((change, context) => {
      // Get an object with the current document value.
      // If the document does not exist, it has been deleted.
      const document = change.after.exists ? change.after.data() : null;

      // Get an object with the previous document value (for update or delete)
      const oldDocument = change.before.data();

      // perform desired operations ...
    });

Odczytywanie i zapisywanie danych

Gdy funkcja zostanie wywołana, udostępni migawkę danych związanych z wydarzeniem. Możesz użyć tego migawki, aby odczytać lub zapisać dokument, który wywołał zdarzenie, albo użyć pakietu Firebase Admin SDK, aby uzyskać dostęp do innych części bazy danych.

Dane zdarzenia

Odczytywanie danych

Gdy funkcja jest wywoływana, możesz chcieć pobrać dane z dokumentu, który został zaktualizowany, lub pobrać dane sprzed aktualizacji. Poprzednie dane możesz uzyskać za pomocą funkcji change.before.data(), która zawiera zrzut dokumentu sprzed aktualizacji. Podobnie change.after.data() zawiera stan zrzutu dokumentu po aktualizacji.

Node.js

exports.updateUser2 = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
      // Get an object representing the current document
      const newValue = change.after.data();

      // ...or the previous value before this update
      const previousValue = change.before.data();
    });

Dostęp do właściwości jest taki sam jak w przypadku innych obiektów. Możesz też użyć funkcji get, aby uzyskać dostęp do określonych pól:

Node.js

// Fetch data using standard accessors
const age = snap.data().age;
const name = snap.data()['name'];

// Fetch data using built in accessor
const experience = snap.get('experience');

Zapisywanie danych

Każde wywołanie funkcji jest powiązane z konkretnym dokumentem w bazie danychCloud Firestore. Dostęp do tego dokumentu możesz uzyskać jako DocumentReference we właściwości ref przeglądu dnia zwróconego do funkcji.

Ten DocumentReference pochodzi z Cloud Firestorepakietu SDK Node.js i zawiera metody takie jak update(), set() i remove(), dzięki czemu możesz łatwo modyfikować dokument, który wywołał funkcję.

Node.js

// Listen for updates to any `user` document.
exports.countNameChanges = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
      // Retrieve the current and previous value
      const data = change.after.data();
      const previousData = change.before.data();

      // We'll only update if the name has changed.
      // This is crucial to prevent infinite loops.
      if (data.name == previousData.name) {
        return null;
      }

      // Retrieve the current count of name changes
      let count = data.name_change_count;
      if (!count) {
        count = 0;
      }

      // Then return a promise of a set operation to update the count
      return change.after.ref.set({
        name_change_count: count + 1
      }, {merge: true});
    });

Dane spoza zdarzenia wywołującego

Cloud Functions wykonywać w zaufanym środowisku, co oznacza, że są autoryzowane jako konto usługi w Twoim projekcie. Możesz wykonywać odczyty i zapisy za pomocą pakietu Firebase Admin SDK:

Node.js

const admin = require('firebase-admin');
admin.initializeApp();

const db = admin.firestore();

exports.writeToFirestore = functions.firestore
  .document('some/doc')
  .onWrite((change, context) => {
    db.doc('some/otherdoc').set({ ... });
  });

Ograniczenia

Pamiętaj o tych ograniczeniach dotyczących wyzwalaczy Cloud Firestore dla Cloud Functions:

  • Cloud Functions (1 generacji) wymaga istniejącej bazy danych „(default)” w trybie natywnym Firestore. Nie obsługuje Cloud Firestore nazwanych baz danych ani trybu Datastore. W takich przypadkach do konfigurowania zdarzeń używaj urządzenia Cloud Functions (2 generacji).
  • Kolejność nie jest gwarantowana. Szybkie zmiany mogą powodować wywoływanie funkcji w nieoczekiwanej kolejności.
  • Zdarzenia są dostarczane co najmniej raz, ale jedno zdarzenie może spowodować wielokrotne wywołanie funkcji. Unikaj polegania na mechanizmach przetwarzania dokładnie raz i pisz funkcje idempotentne.
  • Cloud Firestore w trybie Datastore wymaga Cloud Functions (2 generacji). Usługa Cloud Functions (1 generacji) nie obsługuje trybu Datastore.
  • Aktywator jest powiązany z 1 bazą danych. Nie możesz utworzyć aktywatora, który pasuje do wielu baz danych.
  • Usunięcie bazy danych nie powoduje automatycznego usunięcia żadnych wyzwalaczy tej bazy. Wywoływacz przestaje dostarczać zdarzenia, ale nadal istnieje, dopóki go nie usuniesz.
  • Jeśli pasujące zdarzenie przekracza maksymalny rozmiar żądania, może nie zostać dostarczone do Cloud Functions (1 generacji).
    • Zdarzenia, które nie zostały dostarczone z powodu rozmiaru żądania, są rejestrowane w logach platformy i wliczane do wykorzystania logów w projekcie.
    • Te logi znajdziesz w Eksploratorze logów z komunikatem „Event cannot deliver to Cloud function due to size exceeding the limit for 1st gen..." o error poziomie ważności. Nazwę funkcji znajdziesz w polu functionName. Jeśli pole receiveTimestamp nadal mieści się w zakresie godziny od teraz, możesz wywnioskować rzeczywistą treść wydarzenia, czytając dany dokument z migawką przed i po sygnaturze czasowej.
    • Aby uniknąć takiej częstotliwości:
      • Migracja i uaktualnienie do Cloud Functions (2 generacji)
      • Zmniejszanie rozmiaru dokumentu
      • Usuń Cloud Functions, o które chodzi.
    • Możesz wyłączyć logowanie za pomocą wykluczeń, ale pamiętaj, że problematyczne zdarzenia nadal nie będą dostarczane.