Rozszerzanie Cloud Firestore za pomocą Cloud Functions (2 generacji)

Dzięki Cloud Functions możesz wdrażać kod, aby obsługiwać zdarzenia wywoływane przez zmiany w bazie danych Cloud Firestore. Dzięki temu możesz łatwo dodawać do aplikacji funkcje po stronie serwera bez konieczności uruchamiania własnych serwerów.

Cloud Functions (2 generacji)

Dzięki Cloud Run i Eventarc funkcja Cloud Functions for Firebase (2 generacji) zapewnia wydajniejszą infrastrukturę, zaawansowane zarządzanie wydajnością i skalowalnością oraz większą kontrolę nad środowiskiem wykonawczym funkcji. Więcej informacji o wersji 2 generacji znajdziesz w artykule Cloud Functions for Firebase (2nd gen). Więcej informacji o funkcjach pierwszej generacji znajdziesz w artykule Rozszerzanie Cloud Firestore za pomocą Cloud Functions.

Cloud Firestore reguł aktywacji funkcji

Pakiet SDK Cloud Functions for Firebase eksportuje te Cloud Firestore aktywatorów zdarzeń, aby umożliwić tworzenie modułów obsługi związanych z określonymi Cloud Firestore zdarzeniami:

Node.js

Typ zdarzenia Aktywator
onDocumentCreated Wywoływane, gdy dokument jest zapisywany po raz pierwszy.
onDocumentUpdated Wyzwalane, gdy dokument już istnieje i jego jakakolwiek wartość ulegnie zmianie.
onDocumentDeleted Wywoływane po usunięciu dokumentu.
onDocumentWritten Wywoływane, gdy zostanie wywołane zdarzenie onDocumentCreated, onDocumentUpdated lub onDocumentDeleted.
onDocumentCreatedWithAuthContext onDocumentCreated z dodatkowymi informacjami uwierzytelniania
onDocumentWrittenWithAuthContext onDocumentWritten z dodatkowymi informacjami uwierzytelniania
onDocumentDeletedWithAuthContext onDocumentDeleted z dodatkowymi informacjami uwierzytelniania
onDocumentUpdatedWithAuthContext onDocumentUpdated z dodatkowymi informacjami uwierzytelniania

Python (wersja testowa)

Typ zdarzenia Aktywator
on_document_created Wywoływane, gdy dokument jest zapisywany po raz pierwszy.
on_document_updated Wyzwalane, gdy dokument już istnieje i jego jakakolwiek wartość ulegnie zmianie.
on_document_deleted Wywoływane po usunięciu dokumentu.
on_document_written Wywoływane, gdy zostanie wywołane zdarzenie on_document_created, on_document_updated lub on_document_deleted.
on_document_created_with_auth_context on_document_created z dodatkowymi informacjami uwierzytelniania
on_document_updated_with_auth_context on_document_updated z dodatkowymi informacjami uwierzytelniania
on_document_deleted_with_auth_context on_document_deleted z dodatkowymi informacjami uwierzytelniania
on_document_written_with_auth_context on_document_written z dodatkowymi informacjami uwierzytelniania

Cloud Firestore zdarzenia są wywoływane tylko w przypadku zmian w dokumentach. Aktualizacja dokumentu Cloud Firestore, w którym dane nie uległy zmianie (nieoperacyjna operacja zapisu), nie generuje zdarzenia aktualizacji ani zapisu. Nie można dodawać zdarzeń do określonych pól.

Jeśli nie masz jeszcze włączonego projektu w usłudze Cloud Functions for Firebase, przeczytaj artykuł Pierwsze kroki z usługą Cloud Functions for Firebase (2 generacji), aby skonfigurować projekt Cloud Functions for Firebase.

Pisanie funkcji wywoływanych przez Cloud Firestore

Definiowanie wyzwalacza funkcji

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

Node.js

import {
  onDocumentWritten,
  onDocumentCreated,
  onDocumentUpdated,
  onDocumentDeleted,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("my-collection/{docId}", (event) => {
   /* ... */ 
});

Python (wersja testowa)

from firebase_functions.firestore_fn import (
  on_document_created,
  on_document_deleted,
  on_document_updated,
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_created(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot]) -> None:

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

Określ pojedynczy dokument

Jeśli chcesz wywołać zdarzenie dla dowolnej zmiany w konkretnym dokumencie, możesz użyć tej funkcji.

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("users/marie", (event) => {
  // Your code here
});

Python (wersja testowa)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/marie")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:

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

Jeśli chcesz zastosować regułę do grupy dokumentów, np. do dowolnego dokumentu w konkretnej kolekcji, zamiast identyfikatora dokumentu użyj {wildcard}:

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("users/{userId}", (event) => {
  // If we set `/users/marie` to {name: "Marie"} then
  // event.params.userId == "marie"
  // ... and ...
  // event.data.after.data() == {name: "Marie"}
});

Python (wersja testowa)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # If we set `/users/marie` to {name: "Marie"} then
  event.params["userId"] == "marie"  # True
  # ... and ...
  event.data.after.to_dict() == {"name": "Marie"}  # True

W tym przykładzie, gdy zmieni się dowolne pole w dowolnym dokumencie w users, zostanie ono dopasowane do znaku zapytania o nazwie userId.

Jeśli dokument w users ma podkolekcje, a w jednym z dokumentów w tych podkolekcjach zmieni się pole, symbol wieloznaczny userId nie zostanie uruchomiony.

Dopasowania do symboli wieloznacznych są wyodrębniane z ścieżki dokumentu i przechowywane w event.params. Możesz zdefiniować dowolną liczbę symboli wieloznacznych, aby zastąpić jawne identyfikatory zbiorów lub dokumentów, na przykład:

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("users/{userId}/{messageCollectionId}/{messageId}", (event) => {
    // If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
    // event.params.userId == "marie";
    // event.params.messageCollectionId == "incoming_messages";
    // event.params.messageId == "134";
    // ... and ...
    // event.data.after.data() == {body: "Hello"}
});

Python (wersja testowa)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/{userId}/{messageCollectionId}/{messageId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
  event.params["userId"] == "marie"  # True
  event.params["messageCollectionId"] == "incoming_messages"  # True
  event.params["messageId"] == "134"  # True
  # ... and ...
  event.data.after.to_dict() == {"body": "Hello"}

Wyzwalacz musi zawsze wskazywać dokument, nawet jeśli używasz symbolu wieloznacznego. Na przykład users/{userId}/{messageCollectionId} jest nieprawidłowe, ponieważ {messageCollectionId} jest zbiorem. Jednak users/{userId}/{messageCollectionId}/{messageId} jest prawidłowy, ponieważ {messageId} zawsze wskazuje dokument.

Aktywatory zdarzeń

Wywoływanie funkcji po utworzeniu nowego dokumentu

Funkcję możesz aktywować w każdej chwili, gdy w kolekcji zostanie utworzony nowy dokument. Ta przykładowa funkcja jest wywoływana za każdym razem, gdy dodawany jest nowy profil użytkownika:

Node.js

import {
  onDocumentCreated,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.createuser = onDocumentCreated("users/{userId}", (event) => {
    // Get an object representing the document
    // e.g. {'name': 'Marie', 'age': 66}
    const snapshot = event.data;
    if (!snapshot) {
        console.log("No data associated with the event");
        return;
    }
    const data = snapshot.data();

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

    // perform more operations ...
});

Aby uzyskać dodatkowe informacje uwierzytelniające, użyj onDocumentCreatedWithAuthContext.

Python (wersja testowa)

from firebase_functions.firestore_fn import (
  on_document_created,
  Event,
  DocumentSnapshot,
)

@on_document_created(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot]) -> None:
  # Get a dictionary representing the document
  # e.g. {'name': 'Marie', 'age': 66}
  new_value = event.data.to_dict()

  # Access a particular field as you would any dictionary
  name = new_value["name"]

  # Perform more operations ...

Uruchamianie funkcji po zaktualizowaniu dokumentu

Możesz też ustawić funkcję, która będzie wykonywana po zaktualizowaniu dokumentu. Ta przykładowa funkcja jest wywoływana, gdy użytkownik zmieni profil:

Node.js

import {
  onDocumentUpdated,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.updateuser = onDocumentUpdated("users/{userId}", (event) => {
    // Get an object representing the document
    // e.g. {'name': 'Marie', 'age': 66}
    const newValue = event.data.after.data();

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

    // perform more operations ...
});

Aby uzyskać dodatkowe informacje uwierzytelniające, użyj onDocumentUpdatedWithAuthContext.

Python (wersja testowa)

from firebase_functions.firestore_fn import (
  on_document_updated,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # Get a dictionary representing the document
  # e.g. {'name': 'Marie', 'age': 66}
  new_value = event.data.after.to_dict()

  # Access a particular field as you would any dictionary
  name = new_value["name"]

  # Perform more operations ...

Uruchamianie funkcji po usunięciu dokumentu

Możesz też uruchomić funkcję po usunięciu dokumentu. Ta przykładowa funkcja jest wywoływana, gdy użytkownik usuwa swój profil:

Node.js

import {
  onDocumentDeleted,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.deleteuser = onDocumentDeleted("users/{userId}", (event) => {
    // Get an object representing the document
    // e.g. {'name': 'Marie', 'age': 66}
    const snap =  event.data;
    const data =  snap.data();

    // perform more operations ...
});

Aby uzyskać dodatkowe informacje uwierzytelniające, użyj onDocumentDeletedWithAuthContext.

Python (wersja testowa)

from firebase_functions.firestore_fn import (
  on_document_deleted,
  Event,
  DocumentSnapshot,
)

@on_document_deleted(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot|None]) -> None:
  # Perform more operations ...

Aktywowanie funkcji po każdej zmianie 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ą reguły wywoływania zdarzenia „document written”. Ta przykładowa funkcja jest wywoływana, gdy użytkownik zostanie utworzony, zaktualizowany lub usunięty:

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.modifyuser = onDocumentWritten("users/{userId}", (event) => {
    // Get an object with the current document values.
    // If the document does not exist, it was deleted
    const document =  event.data.after.data();

    // Get an object with the previous document values
    const previousValues =  event.data.before.data();

    // perform more operations ...
});

Aby uzyskać dodatkowe informacje uwierzytelniające, użyj onDocumentWrittenWithAuthContext.

Python (wersja testowa)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot | None]]) -> None:
  # Get an object with the current document values.
  # If the document does not exist, it was deleted.
  document = (event.data.after.to_dict()
              if event.data.after is not None else None)

  # Get an object with the previous document values.
  # If the document does not exist, it was newly created.
  previous_values = (event.data.before.to_dict()
                     if event.data.before is not None else None)

  # Perform more operations ...

Odczytywanie i zapisywanie danych

Gdy funkcja zostanie wywołana, udostępnia migawkę danych powiązanych z wydarzeniem. Możesz użyć tego migawka do odczytu lub zapisu w dokumencie, który wywołał zdarzenie, albo użyć pakietu Firebase Admin SDK, aby uzyskać dostęp do innych części bazy danych.

Dane zdarzenia

Czytanie danych

Gdy funkcja zostanie wywołana, możesz pobrać dane z dokumentu, który został zaktualizowany, lub dane przed aktualizacją. Poprzednie dane możesz uzyskać, korzystając z klucza event.data.before, który zawiera zrzut dokumentu sprzed aktualizacji. Podobnie event.data.after zawiera stan zrzutu dokumentu po aktualizacji.

Node.js

exports.updateuser2 = onDocumentUpdated("users/{userId}", (event) => {
    // Get an object with the current document values.
    // If the document does not exist, it was deleted
    const newValues =  event.data.after.data();

    // Get an object with the previous document values
    const previousValues =  event.data.before.data();
});

Python (wersja testowa)

@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # Get an object with the current document values.
  new_value = event.data.after.to_dict()

  # Get an object with the previous document values.
  prev_value = event.data.before.to_dict()

Do właściwości możesz uzyskać dostęp tak samo jak w przypadku każdego innego obiektu. 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 = event.data.after.data().age;
const name = event.data.after.data()['name'];

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

Python (wersja testowa)

# Get the value of a single document field.
age = event.data.after.get("age")

# Convert the document to a dictionary.
age = event.data.after.to_dict()["age"]

Zapisywanie danych

Każde wywołanie funkcji jest powiązane z konkretnym dokumentem w Twojej bazie danych Cloud Firestore. Dostęp do tego dokumentu uzyskasz w przeglądzie zaktualizowanym przez funkcję.

Dokumentacja dokumentu zawiera metody takie jak update(), set() i remove(), dzięki którym możesz modyfikować dokument, który wywołał funkcję.

Node.js

import { onDocumentUpdated } from "firebase-functions/v2/firestore";

exports.countnamechanges = onDocumentUpdated('users/{userId}', (event) => {
  // Retrieve the current and previous value
  const data = event.data.after.data();
  const previousData = event.data.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 data.after.ref.set({
    name_change_count: count + 1
  }, {merge: true});

});

Python (wersja testowa)

@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # Get the current and previous document values.
  new_value = event.data.after
  prev_value = event.data.before

  # We'll only update if the name has changed.
  # This is crucial to prevent infinite loops.
  if new_value.get("name") == prev_value.get("name"):
      return

  # Retrieve the current count of name changes
  count = new_value.to_dict().get("name_change_count", 0)

  # Update the count
  new_value.reference.update({"name_change_count": count + 1})

Uzyskiwanie dostępu do informacji o uwierzytelnieniu użytkowników

Jeśli używasz jednego z tych typów zdarzeń, możesz uzyskać informacje o uwierzytelnianiu użytkownika dotyczące podmiotu, który wywołał zdarzenie. Te informacje są dodatkiem do informacji zwracanych w zdarzeniu podstawowym.

Node.js

  • onDocumentCreatedWithAuthContext
  • onDocumentWrittenWithAuthContext
  • onDocumentDeletedWithAuthContext
  • onDocumentUpdatedWithAuthContext

Python (wersja testowa)

  • on_document_created_with_auth_context
  • on_document_updated_with_auth_context
  • on_document_deleted_with_auth_context
  • on_document_written_with_auth_context

Informacje o danych dostępnych w kontekście uwierzytelniania znajdziesz w artykule Kontekst uwierzytelniania. Ten przykład pokazuje, jak pobierać informacje uwierzytelniania:

Node.js

import { onDocumentWrittenWithAuthContext } from "firebase-functions/v2/firestore"

exports.syncUser = onDocumentWrittenWithAuthContext("users/{userId}", (event) => {
    const snapshot = event.data.after;
    if (!snapshot) {
        console.log("No data associated with the event");
        return;
    }
    const data = snapshot.data();

    // retrieve auth context from event
    const { authType, authId } = event;

    let verified = false;
    if (authType === "system") {
      // system-generated users are automatically verified
      verified = true;
    } else if (authType === "unknown" || authType === "unauthenticated") {
      // admin users from a specific domain are verified
      if (authId.endsWith("@example.com")) {
        verified = true;
      }
    }

    return data.after.ref.set({
        created_by: authId,
        verified,
    }, {merge: true}); 
}); 

Python (wersja testowa)

@on_document_updated_with_auth_context(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:

  # Get the current and previous document values.
  new_value = event.data.after
  prev_value = event.data.before

  # Get the auth context from the event
  user_auth_type = event.auth_type
  user_auth_id = event.auth_id

Dane spoza zdarzenia wywołującego

Cloud Functions działają w zaufanym środowisku. Są one autoryzowane jako konto usługi w Twoim projekcie, a Ty możesz wykonywać operacje odczytu i zapisu za pomocą pakietu SDK Firebase Admin:

Node.js

const { initializeApp } = require('firebase-admin/app');
const { getFirestore, Timestamp, FieldValue } = require('firebase-admin/firestore');

initializeApp();
const db = getFirestore();

exports.writetofirestore = onDocumentWritten("some/doc", (event) => {
    db.doc('some/otherdoc').set({ ... });
  });

  exports.writetofirestore = onDocumentWritten('users/{userId}', (event) => {
    db.doc('some/otherdoc').set({
      // Update otherdoc
    });
  });

Python (wersja testowa)

from firebase_admin import firestore, initialize_app
import google.cloud.firestore

initialize_app()

@on_document_written(document="some/doc")
def myfunction(event: Event[Change[DocumentSnapshot | None]]) -> None:
  firestore_client: google.cloud.firestore.Client = firestore.client()
  firestore_client.document("another/doc").set({
      # ...
  })

Ograniczenia

Pamiętaj o tych ograniczeniach dotyczących Cloud Firestore w przypadku Cloud Functions:

  • Cloud Functions (pierwsza generacja) wymaga istniejącej bazy danych „(domyślna)” w trybie natywnym Firestore. Nie obsługuje ona baz danych o nazwie Cloud Firestore ani trybu Datastore. W takich przypadkach do konfigurowania zdarzeń użyj Cloud Functions (2 generacji).
  • Zamówienie nie jest gwarantowane. Nagłe zmiany mogą powodować wywołania funkcji w nieoczekiwanej kolejności.
  • Zdarzenia są wysyłane co najmniej raz, ale pojedyncze zdarzenie może spowodować wielokrotne wywołanie funkcji. Unikaj zależności od mechanizmu „dokładnie raz” i pisz funkcje idempotentne.
  • Usługa Cloud Firestore w trybie Datastore wymaga Cloud Functions (2 generacji). Cloud Functions (1 generacji) nie obsługuje trybu Datastore.
  • Aktywator jest powiązany z jedną bazą danych. Nie możesz utworzyć reguły, która pasuje do wielu baz danych.
  • Usunięcie bazy danych nie powoduje automatycznego usunięcia żadnych jej reguł. Wyzwalacz przestaje dostarczać zdarzenia, ale nadal istnieje, dopóki go nie usuniesz.
  • Jeśli dopasowane 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 poważnym error. Nazwa funkcji znajduje się w polu functionName. Jeśli receiveTimestamp pole jest nadal w ciągu godziny od bieżącej chwili, możesz określić rzeczywistą treść zdarzenia, czytając odpowiedni dokument ze zrzutem ekranu przed i po sygnaturze czasową.
    • Aby uniknąć takiej częstotliwości, możesz:
      • Przeprowadź migrację i przejdź na Cloud Functions (2 generacji)
      • zmniejszyć rozmiar dokumentu,
      • Usuń Cloud Functions
    • Możesz wyłączyć samo rejestrowanie za pomocą wykluczeń, ale pamiętaj, że nieprawidłowe zdarzenia nadal nie będą dostarczane.