Aktywatory Cloud Firestore

Dzięki Cloud Functions możesz obsługiwać zdarzenia w Cloud Firestore bez konieczności aktualizowania kodu klienta. Możesz wprowadzać zmiany w Cloud Firestore za pomocą interfejsu migawki dokumentu lub za pomocą 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 migawkę danych przechowywanych w określonym dokumencie. W przypadku zdarzeń zapisu lub aktualizacji obiekt danych zawiera 2 migawki, które reprezentują stan danych przed i po zdarzeniu wywołującym.

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

Aktywatory funkcji Cloud Firestore

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

Node.js

Typ zdarzenia Aktywator
onDocumentCreated Wywoływane, gdy dokument jest zapisywany po raz pierwszy.
onDocumentUpdated Wywoływane, gdy dokument już istnieje i zmieniła się jego wartość.
onDocumentDeleted Wywoływane, gdy dokument zostanie usunięty.
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ływane, gdy dokument jest zapisywany po raz pierwszy.
on_document_updated Wywoływane, gdy dokument już istnieje i zmieniła się jego wartość.
on_document_deleted Wywoływane, gdy dokument zostanie usunięty.
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

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

Pisanie funkcji wywoływanych 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 z symbolem wieloznacznym.

Określanie pojedynczego dokumentu

Jeśli chcesz wywoływać zdarzenie przy każdej zmianie 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 powiązać aktywator z grupą dokumentów, np. z dowolnym dokumentem w określonej kolekcji, użyj {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 zmieni się dowolne pole w dowolnym dokumencie w kolekcji users, zostanie dopasowany symbol wieloznaczny o nazwie userId.

Jeśli dokument w kolekcji users ma podkolekcje i zmieni się pole w jednym z dokumentów podkolekcji, 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ć nimi jawne 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"}

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

Aktywatory zdarzeń

Wywoływanie funkcji po utworzeniu nowego dokumentu

Możesz wywoływać funkcję za każdym razem, 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

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

Wywoływanie funkcji po zaktualizowaniu dokumentu

Możesz też wywoływać funkcję po zaktualizowaniu dokumentu. 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 ...
});

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

Wywoływanie funkcji po usunięciu dokumentu

Możesz też wywoływać funkcję po usunięciu dokumentu. Ta przykładowa funkcja jest wywoływana, gdy użytkownik usunie 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 ...
});

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

Wywoływanie funkcji przy wszystkich zmianach 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 „zapis dokumentu”. 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 ...
});

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

Gdy funkcja zostanie wywołana, udostępnia migawkę danych związanych ze zdarzeniem. Możesz użyć tej migawki 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

Odczytywanie danych

Gdy funkcja zostanie wywołana, możesz chcieć pobrać dane z zaktualizowanego dokumentu lub dane sprzed aktualizacji. Poprzednie dane możesz uzyskać za pomocą event.data.before, które zawiera zrzut dokumentu sprzed aktualizacji. Podobnie event.data.after zawiera stan migawki 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()

Do właściwości możesz uzyskiwać dostęp tak 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 migawce zwróconej do funkcji.

Odwołanie do dokumentu zawiera metody takie jak update(), set() i 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})

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ą dodatkiem 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. Ten przykład pokazuje, jak pobrać informacje o uwierzytelnianiu:

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 działają w zaufanym środowisku. Są autoryzowane jako konto usługi w Twoim projekcie i możesz wykonywać odczyty i zapisy 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 aktywatorów 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 skonfiguruj zdarzenia za pomocą Cloud Functions (2 generacji).
  • Konfiguracja międzyprojektowa z aktywatorem Cloud Functions i Cloud Firestore jest ograniczona. Aby skonfigurować aktywator Cloud Firestore, Cloud Functions muszą znajdować się w tym samym projekcie.
  • Kolejność nie jest gwarantowana. Szybkie zmiany mogą wywoływać funkcje w nieoczekiwanej kolejności.
  • Zdarzenia są dostarczane co najmniej raz, ale pojedyncze zdarzenie może spowodować wiele wywołań funkcji. Nie polegaj na mechanizmach „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 aktywatorów tej bazy danych. Aktywator przestaje dostarczać zdarzenia, ale nadal istnieje, dopóki go nie usuniesz.
  • Jeśli dopasowane zdarzenie przekracza maksymalny rozmiar żądania, zdarzenie 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 poziomie 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 i po sygnaturze czasowej.
    • Aby uniknąć takiej kadencji, możesz:
      • przeprowadzić migrację i uaktualnić do Cloud Functions (2 generacji);
      • zmniejszyć rozmiar dokumentu;
      • usunąć odpowiednie Cloud Functions.
    • Możesz wyłączyć samo logowanie za pomocą wykluczeń ale pamiętaj, że problematyczne zdarzenia nadal nie będą dostarczane.