Aktywatory Cloud Firestore

Dzięki Cloud Functions możesz obsługiwać zdarzenia w Cloud Firestore bez konieczności aktualizowania kodu klienta. Zmiany w Cloud Firestore możesz wprowadzać za pomocą interfejsu zrzutu dokumentu lub pakietu Admin SDK.

W typowym cyklu życia funkcja Cloud Firestore wykonuje te czynności:

  1. Czeka na zmiany w konkretnym dokumencie.
  2. Uruchamia się, gdy wystąpi zdarzenie, i wykonuje swoje zadania.
  3. Otrzymuje obiekt danych, który zawiera zrzut danych przechowywanych w określonym dokumencie. W przypadku zdarzeń zapisu lub aktualizacji obiekt danych zawiera 2 zrzuty, które reprezentują stan danych przed zdarzeniem wywołującym i po nim.

Odległość między lokalizacją instancji Firestore a lokalizacją funkcji może powodować znaczne opóźnienia sieci. Aby zoptymalizować wydajność, rozważ określenie lokalizacji funkcji, jeśli ma to zastosowanie.

Aktywatory funkcji Cloud Firestore

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

Node.js

Typ zdarzenia Aktywator
onDocumentCreated Uruchamiana, gdy dokument jest zapisywany po raz pierwszy.
onDocumentUpdated Uruchamiane, gdy dokument już istnieje i zmieniła się dowolna wartość.
onDocumentDeleted Uruchamiany po usunięciu dokumentu.
onDocumentWritten Wywoływane, gdy wywoływane są reguły onDocumentCreated, onDocumentUpdated lub onDocumentDeleted.
onDocumentCreatedWithAuthContext onDocumentCreated z dodatkowymi danymi uwierzytelniającymi
onDocumentWrittenWithAuthContext onDocumentWritten z dodatkowymi danymi uwierzytelniającymi
onDocumentDeletedWithAuthContext onDocumentDeleted z dodatkowymi danymi uwierzytelniającymi
onDocumentUpdatedWithAuthContext onDocumentUpdated z dodatkowymi danymi uwierzytelniającymi

Python

Typ zdarzenia Aktywator
on_document_created Uruchamiana, gdy dokument jest zapisywany po raz pierwszy.
on_document_updated Uruchamiane, gdy dokument już istnieje i zmieniła się dowolna wartość.
on_document_deleted Uruchamiany po usunięciu dokumentu.
on_document_written Wywoływane, gdy wywoływane są reguły on_document_created, on_document_updated lub on_document_deleted.
on_document_created_with_auth_context on_document_created z dodatkowymi danymi uwierzytelniającymi
on_document_updated_with_auth_context on_document_updated z dodatkowymi danymi uwierzytelniającymi
on_document_deleted_with_auth_context on_document_deleted z dodatkowymi danymi uwierzytelniającymi
on_document_written_with_auth_context on_document_written z dodatkowymi danymi uwierzytelniającymi

Zdarzenia Cloud Firestore są aktywowane tylko w przypadku zmian w dokumentach. Aktualizacja dokumentu Cloud Firestore, w którym dane nie uległy zmianie (zapis bez operacji), 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ł Pierwsze kroki z Cloud Functions for Firebase (2 generacji), aby skonfigurować projekt Cloud Functions for Firebase.

Pisanie funkcji aktywowanych przez Cloud Firestore

Definiowanie aktywatora funkcji

Aby zdefiniować aktywator 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śl pojedynczy dokument

Jeśli chcesz wywołać zdarzenie w przypadku dowolnej zmiany w konkretnym 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ć aktywator do grupy dokumentów, np. do dowolnego dokumentu w określonej kolekcji, zamiast identyfikatora dokumentu użyj {wildcard}:

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

Jeśli dokument w kolekcji users ma podkolekcje i pole w jednym z dokumentów tych podkolekcji zostanie zmienione, symbol wieloznaczny userId nie zostanie aktywowany.

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ć jawne identyfikatory kolekcji lub dokumentów, na przykład:

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

Reguła musi zawsze wskazywać dokument, nawet jeśli używasz symbolu wieloznacznego. Na przykład users/{userId}/{messageCollectionId} jest nieprawidłowy, 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ę, 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 ...
});

Aby uzyskać dodatkowe informacje o uwierzytelnianiu, użyj 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 uruchamiana, gdy użytkownik zmienia 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 ...
});

Aby uzyskać dodatkowe informacje o uwierzytelnianiu, użyj 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. Ta przykładowa funkcja jest uruchamiana, gdy użytkownik usunie swój profil użytkownika:

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

Aby uzyskać dodatkowe informacje o uwierzytelnianiu, użyj 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 interesuje Cię typ wywoływanego zdarzenia, możesz nasłuchiwać wszystkich zmian w dokumencie Cloud Firestore za pomocą aktywatora zdarzenia „zapisano dokument”. Ta przykładowa funkcja jest uruchamiana, 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 ...
});

Aby uzyskać dodatkowe informacje o uwierzytelnianiu, użyj 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

Po aktywowaniu funkcja udostępnia zrzut danych związanych ze zdarzeniem. Możesz użyć tego zrzutu, aby odczytać lub zapisać dokument, który wywołał zdarzenie, lub użyć pakietu Firebase Admin SDK, aby uzyskać dostęp do innych części bazy danych.

Dane zdarzenia

Dane na temat czytania

Po aktywowaniu funkcji możesz pobrać dane ze zaktualizowanego dokumentu lub dane sprzed aktualizacji. Możesz uzyskać wcześniejsze dane, używając 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 każdego innego obiektu. 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 danych Cloud Firestore. Możesz uzyskać dostęp do tego dokumentu w zdjęciu zwróconym do funkcji.

Dokument zawiera metody takie jak update(), set() i remove(), dzięki czemu możesz zmodyfikować 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})

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ą uzupełnieniem 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 są wykonywane w zaufanym środowisku. Są one autoryzowane jako konto usługi w projekcie i możesz wykonywać operacje odczytu i zapisu 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 aktywatorów Cloud Firestore w 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 do konfigurowania zdarzeń używaj Cloud Functions (2 generacji).
  • Konfiguracja między projektami z aktywatorami Cloud Functions i Cloud Firestore jest ograniczona. Aby skonfigurować aktywator Cloud Firestore, Cloud Functions musi być w tym samym projekcie.
  • Kolejność nie jest gwarantowana. Szybkie zmiany mogą spowodować wywołanie funkcji w nieoczekiwanej kolejności.
  • Zdarzenia są dostarczane co najmniej raz, ale pojedyncze zdarzenie może spowodować wielokrotne wywołanie funkcji. Unikaj polegania na mechanizmach gwarantujących dostarczenie dokładnie raz i pisz funkcje idempotentne.
  • 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ć aktywatora, który pasuje do wielu baz danych.
  • Usunięcie bazy danych nie powoduje automatycznego usunięcia żadnych aktywatorów tej bazy danych. Aktywator przestanie dostarczać zdarzenia, ale będzie nadal istnieć, 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 „Nie można dostarczyć zdarzenia do funkcji w Cloud Functions, ponieważ rozmiar przekracza limit dla 1 generacji…” o ważności error. Nazwę funkcji znajdziesz w polu functionName. Jeśli pole receiveTimestamp nadal mieści się w ciągu godziny od teraz, możesz wywnioskować rzeczywistą treść zdarzenia, odczytując dany dokument z migawką przed sygnaturą czasową i po niej.
    • Aby uniknąć takiej sytuacji:
      • Migracja i uaktualnienie do Cloud Functions (2 generacji)
      • Zmniejsz rozmiar dokumentu.
      • Usuń dany element Cloud Functions
    • Możesz wyłączyć samo logowanie za pomocą wykluczeń, ale pamiętaj, że zdarzenia powodujące błędy nadal nie będą dostarczane.