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

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

Cloud Functions (2 generacji)

Dzięki wykorzystaniu Cloud Run i Eventarc Cloud Functions dla Firebase (2 generacji) zapewnia lepszą infrastrukturę, zaawansowaną kontrolę nad wydajnością i skalowalnością oraz większą kontrolę nad środowiskiem wykonawczym funkcji. Więcej informacji o 2 generacji znajdziesz w artykule na temat Cloud Functions dla Firebase (2 generacji). Więcej informacji o 1 generacji znajdziesz w artykule o rozszerzaniu Cloud Firestore przy użyciu Cloud Functions.

Aktywatory funkcji Cloud Firestore

Pakiet SDK Cloud Functions dla Firebase eksportuje następujące wyzwalacze 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 Wywoływane przy pierwszym zapisie dokumentu.
onDocumentUpdated Wywoływane, gdy dokument już istnieje i ma dowolną wartość.
onDocumentDeleted Wywoływane, gdy dokument jest usuwany.
onDocumentWritten Wywoływane po wywołaniu funkcji onDocumentCreated, onDocumentUpdated lub onDocumentDeleted.
onDocumentCreatedWithAuthContext onDocumentCreated z dodatkowymi informacjami uwierzytelniającymi
onDocumentWrittenWithAuthContext onDocumentWritten z dodatkowymi informacjami uwierzytelniającymi
onDocumentDeletedWithAuthContext onDocumentDeleted z dodatkowymi informacjami uwierzytelniającymi
onDocumentUpdatedWithAuthContext onDocumentUpdated z dodatkowymi informacjami uwierzytelniającymi

Python (wersja testowa)

Typ zdarzenia Aktywator
on_document_created Wywoływane przy pierwszym zapisie dokumentu.
on_document_updated Wywoływane, gdy dokument już istnieje i ma dowolną wartość.
on_document_deleted Wywoływane, gdy dokument jest usuwany.
on_document_written Wywoływane po wywołaniu funkcji on_document_created, on_document_updated lub on_document_deleted.
on_document_created_with_auth_context on_document_created z dodatkowymi informacjami uwierzytelniającymi
on_document_updated_with_auth_context on_document_updated z dodatkowymi informacjami uwierzytelniającymi
on_document_deleted_with_auth_context on_document_deleted z dodatkowymi informacjami uwierzytelniającymi
on_document_written_with_auth_context on_document_written z dodatkowymi informacjami uwierzytelniającymi

Zdarzenia Cloud Firestore są aktywowane tylko po zmianie dokumentu. Aktualizacja dokumentu Cloud Firestore, w której dane pozostają niezmienione (zapis bez operacji), nie powoduje wygenerowania zdarzenia aktualizacji ani zapisu. Nie można dodawać zdarzeń do określonych pól.

Jeśli nie masz jeszcze projektu korzystającego z Cloud Functions dla Firebase, przeczytaj artykuł Pierwsze kroki z Cloud Functions dla Firebase (2 generacji), aby dowiedzieć się, jak skonfigurować projekt Cloud Functions dla Firebase.

Zapisywanie funkcji aktywowanych przez Cloud Firestore

Zdefiniuj aktywator funkcji

Aby zdefiniować aktywator Cloud Firestore, podaj ś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 symboli wieloznacznych.

Określ pojedynczy dokument

Jeśli chcesz wywoływać zdarzenie w przypadku dowolnej zmiany w określonym dokumencie, możesz użyć poniższej 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śl grupę dokumentów za pomocą symboli wieloznacznych

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

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 zmiana dowolnego pola w dokumencie w obiekcie users spowoduje dopasowanie go do symbolu wieloznacznego o nazwie userId.

Jeśli dokument w kolekcji users ma kolekcje podrzędne, a pole w jednym z dokumentów z tych kolekcji zostanie zmienione, symbol wieloznaczny userId nie zostanie aktywowany.

Dopasowania symboli wieloznacznych są wyodrębniane ze ścieżki dokumentu i zapisywane w pliku event.params. Możesz zdefiniować dowolną liczbę symboli wieloznacznych w stosunku do jawnych identyfikatorów dokumentów lub kolekcji, 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} nie jest prawidłowy, ponieważ {messageCollectionId} jest kolekcją. Atrybut users/{userId}/{messageCollectionId}/{messageId} jest jednak prawidłowy, ponieważ {messageId} zawsze wskazuje dokument.

Aktywatory zdarzeń

Aktywowanie funkcji przy tworzeniu nowego dokumentu

Możesz aktywować funkcję za każdym razem, gdy w kolekcji zostanie utworzony nowy dokument. Ta przykładowa funkcja jest wyzwalana po każdym dodaniu nowego profilu 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 dotyczące uwierzytelniania, wpisz 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 ...

Aktywowanie funkcji po zaktualizowaniu dokumentu

Możesz także uruchamiać funkcję po zaktualizowaniu dokumentu. Ta przykładowa funkcja jest uruchamiana, gdy użytkownik zmieni swój 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 dotyczące uwierzytelniania, wpisz 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 ...

Aktywowanie funkcji po usunięciu dokumentu

Możesz też aktywować funkcję po usunięciu dokumentu. Ta przykładowa funkcja jest uruchamiana, gdy użytkownik usunie 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 dotyczące uwierzytelniania, wpisz 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 w przypadku wszystkich zmian w dokumencie

Jeśli nie zależy Ci na typie uruchamianych zdarzeń, możesz nasłuchiwać wszystkich zmian w dokumencie Cloud Firestore za pomocą aktywatora zdarzenia „dokument zapisany”. Ta przykładowa funkcja jest uruchamiana w przypadku utworzenia, zaktualizowania lub usunięcia użytkownika:

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 dotyczące uwierzytelniania, wpisz 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

Po aktywowaniu funkcji wyświetla ona migawkę danych związanych ze zdarzeniem. Możesz użyć tego zrzutu do odczytu lub zapisu w dokumencie, który wywołał zdarzenie, albo za pomocą pakietu Firebase Admin SDK, aby uzyskać dostęp do innych części bazy danych.

Dane zdarzenia

Odczytywanie danych

Po uruchomieniu funkcji możesz pobrać dane z dokumentu, który został zaktualizowany, lub uzyskać je przed aktualizacją. Wcześniejsze dane możesz uzyskać, korzystając z dyrektywy event.data.before, która zawiera zrzut dokumentu przed aktualizacją. 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. Aby uzyskać dostęp do określonych pól, możesz też użyć funkcji get:

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 bazie danych Cloud Firestore. Możesz uzyskać dostęp do tego dokumentu w zrzucie zwróconym do Twojej funkcji.

Dokumentacja dokumentu zawiera metody takie jak update(), set() i remove(), dzięki czemu możesz zmodyfikować dokument, który uruchomił tę 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 uwierzytelniających użytkownika

Jeśli używasz jednego z poniższych typów zdarzeń, możesz uzyskać dostęp do informacji uwierzytelniania użytkownika w przypadku podmiotu zabezpieczeń, 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 na temat kontekstu uwierzytelniania. Poniższy przykład pokazuje, jak pobrać informacje uwierzytelniające:

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 aktywującego

Funkcje w Cloud Functions są wykonywane w zaufanym środowisku. Są one autoryzowane jako konto usługi w Twoim projekcie, a 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 (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 aktywatorów Cloud Firestore dla Cloud Functions:

  • Zamówienie nie jest gwarantowane. Szybkie zmiany mogą aktywować wywołania funkcji w nieoczekiwanej kolejności.
  • Zdarzenia są dostarczane co najmniej raz, ale 1 zdarzenie może powodować wiele wywołań funkcji. Unikaj stosowania funkcji „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.
  • Cloud Functions (1 generacji) działa tylko z bazą danych „(domyślnie)” i nie obsługuje nazwanych baz danych Cloud Firestore. Skonfiguruj zdarzenia dla nazwanych baz danych za pomocą Cloud Functions (2 generacji).
  • Aktywator jest powiązany z jedną bazą danych. Nie można utworzyć aktywatora pasującego do wielu baz danych.
  • Usunięcie bazy danych nie powoduje automatycznego usunięcia jej aktywatorów. Reguła przestanie dostarczać zdarzenia, ale będzie istnieć, dopóki jej nie usuniesz.
  • Jeśli dopasowane zdarzenie przekracza maksymalny rozmiar żądania, może ono nie zostać dostarczone do Cloud Functions (1 generacji).
    • Zdarzenia niedostarczone z powodu rozmiaru żądania są rejestrowane w logach platformy i wliczane do wykorzystania logów w projekcie.
    • W eksploratorze logów możesz znaleźć te logi z komunikatem „Zdarzenie nie może zostać dostarczone do funkcji w Cloud Functions z powodu przekroczenia limitu rozmiaru pierwszej generacji...” o poziomie ważności error. Nazwę funkcji znajdziesz pod polem functionName. Jeśli w polu receiveTimestamp pozostało jeszcze więcej niż godzina, możesz wywnioskować rzeczywistą treść zdarzenia, odczytując dany dokument za pomocą zrzutu ekranu przed sygnaturą czasową i po niej.
    • Aby uniknąć takiej częstotliwości, możesz:
      • Migracja do Cloud Functions (2 generacji) i przejście na nią
      • Zmniejsz rozmiar dokumentu
      • Usunąć dane w Cloud Functions
    • Możesz wyłączyć samo logowanie za pomocą wykluczeń. Pamiętaj jednak, że zdarzenia naruszające zasady nie będą dostarczane.