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

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

Cloud Functions (2nd gen)

Cloud Functions dla Firebase Cloud Functions for Firebase (2 generacji) oparte na Cloud Run i Eventarc zapewniają 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 (2nd gen). Jeśli chcesz dowiedzieć się więcej o 1 generacji, przeczytaj artykuł Rozszerzanie Cloud Firestore za pomocą Cloud Functions.

Cloud Firestore aktywatory funkcji

Pakiet SDK Cloud Functions for Firebase 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 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 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

Cloud Firestore zdarzenia są wywoływane tylko w przypadku zmian w dokumentach. 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, w którym włączono 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 do 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 zawiera podkolekcje, a pole w jednym z dokumentów podkolekcji zostanie zmienione, 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 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 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 „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()

Dostęp do właściwości możesz uzyskać 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. Dostęp do tego dokumentu możesz uzyskać 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żytkowników, które dotyczą podmiotu wywołującego 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 poza zdarzeniem aktywującym

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 istnienia bazy danych „(default)” w Cloud Firestore w trybie natywnym. 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. Unikaj polegania 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:
      • przenieść 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.