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

Za pomocą Cloud Functions możesz wdrażać kod do obsługi zdarzeń wywoływanych przez zmiany w bazie danych Cloud Firestore. Dzięki temu możesz łatwo dodać do aplikacji funkcje po stronie serwera bez konieczności uruchamiania własnych serwerów.

Cloud Functions (2 generacji)

Dzięki Cloud RunEventarc usługa Cloud Functions for Firebase (2 generacji) zapewnia sprawniejszą infrastrukturę, zaawansowane zarządzanie wydajnością i skalowalnością oraz większą kontrolę nad środowiskiem wykonawczym funkcji. Więcej informacji o 2 generacji znajdziesz w artykule Cloud Functions dla Firebase (2 generacji). Więcej informacji o funkcjach pierwszej generacji znajdziesz w artykule Rozszerzanie Cloud Firestore za pomocą Cloud Functions.

Reguły funkcji Cloud Firestore

Pakiet SDK Cloud Functions for Firebase eksportuje te wyzwalacze zdarzeń Cloud Firestore, aby umożliwić Ci tworzenie procedur obsługi powiązanych z określonymi zdarzeniami Cloud Firestore:

Node.js

Typ zdarzenia Aktywator
onDocumentCreated Wywoływana, gdy dokument jest zapisywany po raz pierwszy.
onDocumentUpdated Wywoływane, gdy dokument już istnieje i zmieniła się w nim dowolna wartość.
onDocumentDeleted Wywoływane po usunięciu dokumentu.
onDocumentWritten Wywoływane, gdy zostanie wywołane zdarzenie onDocumentCreated, onDocumentUpdated lub onDocumentDeleted.
onDocumentCreatedWithAuthContext onDocumentCreated z dodatkowymi informacjami o uwierzytelnianiu.
onDocumentWrittenWithAuthContext onDocumentWritten z dodatkowymi informacjami o uwierzytelnianiu.
onDocumentDeletedWithAuthContext onDocumentDeleted z dodatkowymi informacjami o uwierzytelnianiu.
onDocumentUpdatedWithAuthContext onDocumentUpdated z dodatkowymi informacjami o uwierzytelnianiu.

Python

Typ zdarzenia Aktywator
on_document_created Wywoływana, gdy dokument jest zapisywany po raz pierwszy.
on_document_updated Wywoływane, gdy dokument już istnieje i zmieniła się w nim dowolna wartość.
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 o uwierzytelnianiu.
on_document_updated_with_auth_context on_document_updated z dodatkowymi informacjami o uwierzytelnianiu.
on_document_deleted_with_auth_context on_document_deleted z dodatkowymi informacjami o uwierzytelnianiu.
on_document_written_with_auth_context on_document_written z dodatkowymi informacjami o uwierzytelnianiu.

Cloud Firestore zdarzenia są wywoływane tylko w przypadku zmian w dokumencie. Aktualizacja dokumentu Cloud Firestore, w której dane pozostają bez zmian (operacja bez działania), nie generuje zdarzenia aktualizacji ani zapisu. Nie można dodawać zdarzeń do konkretnych pól.

Jeśli nie masz jeszcze projektu z włączoną usługą Cloud Functions for Firebase, przeczytaj artykuł Wprowadzenie do Cloud Functions for Firebase (2 generacji) , aby skonfigurować i przygotować projekt Cloud Functions for Firebase.

Pisanie funkcji wywoływanych przez Cloud Firestore

Definiowanie wyzwalacza funkcji

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

Node.js

const {
  onDocumentWritten,
  onDocumentCreated,
  onDocumentUpdated,
  onDocumentDeleted,
  Change,
  FirestoreEvent
} = require('firebase-functions/v2/firestore');

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

Python

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 wzorca wieloznacznego.

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

const {
  onDocumentWritten,
  Change,
  FirestoreEvent
} = require('firebase-functions/v2/firestore');

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

Python

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 dołączyć wyzwalacz do grupy dokumentów, np. do dowolnego dokumentu w określonej kolekcji, użyj znaku {wildcard} zamiast identyfikatora dokumentu:

Node.js

const {
  onDocumentWritten,
  Change,
  FirestoreEvent
} = require('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

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 jakiekolwiek pole w dowolnym dokumencie w users zostanie zmienione, będzie pasować do symbolu wieloznacznego o nazwie userId.

Jeśli dokument w users zawiera podzbiory i zmienisz pole w jednym z dokumentów podzbiorów, symbol wieloznaczny userId nie zostanie wywołany.

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

Node.js

const {
  onDocumentWritten,
  Change,
  FirestoreEvent
} = require('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

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

Warunek musi zawsze wskazywać dokument, nawet jeśli używasz symbolu wieloznacznego. Na przykład users/{userId}/{messageCollectionId} jest nieprawidłowe, ponieważ {messageCollectionId} jest kolekcją. Jednak users/{userId}/{messageCollectionId}/{messageId} jest prawidłowe, ponieważ {messageId} zawsze będzie wskazywać dokument.

Aktywatory zdarzeń

Wywoływanie funkcji po utworzeniu nowego dokumentu

Możesz aktywować funkcję za każdym razem, gdy w kolekcji zostanie utworzony nowy dokument. Ta przykładowa funkcja jest aktywowana za każdym razem, gdy dodawany jest nowy profil użytkownika:

Node.js

const {
  onDocumentCreated,
  Change,
  FirestoreEvent
} = require('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 ...
});

Dodatkowe informacje o uwierzytelnianiu znajdziesz pod numerem onDocumentCreatedWithAuthContext.

Python

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ż uruchomić funkcję, gdy dokument zostanie zaktualizowany. Ta przykładowa funkcja jest wywoływana, gdy użytkownik zmieni swój profil:

Node.js

const {
  onDocumentUpdated,
  Change,
  FirestoreEvent
} = require('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 ...
});

Dodatkowe informacje o uwierzytelnianiu znajdziesz pod numerem onDocumentUpdatedWithAuthContext.

Python

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ę, gdy dokument zostanie usunięty. Ten przykład funkcji jest wywoływany, gdy użytkownik usuwa swój profil:

Node.js

const {
  onDocumentDeleted,
  Change,
  FirestoreEvent
} = require('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 ...
});

Dodatkowe informacje o uwierzytelnianiu znajdziesz pod numerem onDocumentDeletedWithAuthContext.

Python

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 w przypadku wszystkich zmian w dokumencie

Jeśli nie zależy Ci na typie wywoływanego zdarzenia, możesz nasłuchiwać wszystkich zmian w Cloud Firestoredokumencie za pomocą czynnika uruchamiającego zdarzenie „zapisano dokument”. Ta przykładowa funkcja jest wywoływana, gdy użytkownik zostanie utworzony, zaktualizowany lub usunięty:

Node.js

const {
  onDocumentWritten,
  Change,
  FirestoreEvent
} = require('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 ...
});

Dodatkowe informacje o uwierzytelnianiu znajdziesz pod numerem onDocumentWrittenWithAuthContext.

Python

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, dostarcza migawkę danych związanych z wydarzeniem. Możesz użyć tego stanu, aby odczytać lub zapisać dane 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

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ą parametru event.data.before, który zawiera migawkę 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

@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()

Dostęp do właściwości możesz uzyskać tak samo jak w przypadku innych obiektów. Możesz też użyć funkcji get, aby uzyskać dostęp do konkretnych 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

# 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 bazie danychCloud Firestore. Dostęp do tego dokumentu możesz uzyskać w migawce zwróconej do funkcji.

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

Node.js

const {onDocumentUpdated} = require('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 event.data.after.ref.set({
    name_change_count: count + 1
  }, {merge: true});

});

Python

@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})
wyłączysz tę funkcję, gdy nie będzie już potrzebna.

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

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

Node.js

  • onDocumentCreatedWithAuthContext
  • onDocumentWrittenWithAuthContext
  • onDocumentDeletedWithAuthContext
  • onDocumentUpdatedWithAuthContext

Python

  • 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. Poniższy przykład pokazuje, jak pobrać informacje uwierzytelniające:

Node.js

const {onDocumentWrittenWithAuthContext} = require('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

@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 wykonywać w zaufanym środowisku; Mają one uprawnienia konta usługi w Twoim projekcie, a Ty możesz odczytywać i zapisywać dane za pomocą pakietu Firebase Admin SDK:

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

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 wywołań w przypadku Cloud Functions:

  • Cloud Functions (1 generacji) wymaga istniejącej bazy danych „(default)” w trybie natywnym Firestore. Nie obsługuje baz danych o nazwie Cloud Firestore ani trybu Datastore. W takich przypadkach skonfiguruj zdarzenia za pomocą Cloud Functions(2 generacji).
  • Konfiguracja między projektami z wyzwalaczem Cloud FunctionsCloud Firestore jest ograniczona. Aby skonfigurować Cloud Firestore wyzwalacz Cloud Functions, musi on znajdować się w tym samym projekcie.
  • Nie gwarantujemy możliwości złożenia zamówienia. Szybkie zmiany mogą powodować wywoływanie funkcji w nieoczekiwanej kolejności.
  • Zdarzenia są dostarczane co najmniej raz, ale jedno zdarzenie może spowodować wiele wywołań funkcji. Unikaj polegania na mechanizmach „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 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 wyzwalaczy dla tej bazy danych. Reguła przestanie dostarczać zdarzenia, ale będzie nadal istnieć, dopóki jej 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 1 generacji..." o error severity. Nazwę funkcji znajdziesz w polu functionName. Jeśli pole receiveTimestamp nadal mieści się w zakresie godziny od teraz, możesz wywnioskować rzeczywistą treść zdarzenia, czytając dany dokument z migawką przed i po sygnaturze czasowej.
    • Aby uniknąć takiej kadencji, możesz:
      • 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.