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

Dzięki Cloud Functions możesz wdrożyć kod do obsługi wywoływanych zdarzeń przez zmiany w bazie danych Cloud Firestore. Dzięki temu możesz łatwo dodawać tagi po stronie serwera z funkcjami aplikacji bez konieczności uruchamiania własnych serwerów.

Cloud Functions (2 generacji)

Technologia Cloud Run Eventarc Cloud Functions dla Firebase (2 generacji) zapewnia większą wydajność infrastrukturę, zaawansowaną kontrolę nad wydajnością i skalowalnością oraz wiele więcej nad środowiskiem wykonawczym funkcji. Więcej informacji o 2 generacji znajdziesz na stronie Cloud Functions dla Firebase (2 generacji). Aby zobaczyć więcej o 1 generacji, zobacz Rozszerzanie Cloud Firestore za pomocą Cloud Functions

Aktywatory funkcji Cloud Firestore

Pakiet SDK Cloud Functions dla Firebase wyeksportuje następujące dane: Cloud Firestore aktywatory zdarzeń umożliwiające tworzenie modułów obsługi powiązanych z określonym Cloud Firestore wydarzenia:

Node.js

Typ zdarzenia Aktywator
onDocumentCreated Wywoływane, gdy dokument jest zapisany po raz pierwszy.
onDocumentUpdated Wywoływane, gdy dokument już istnieje, ale jego wartość uległa zmianie.
onDocumentDeleted Wywoływane po usunięciu dokumentu.
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, gdy dokument jest zapisany po raz pierwszy.
on_document_updated Wywoływane, gdy dokument już istnieje, ale jego wartość uległa zmianie.
on_document_deleted Wywoływane po usunięciu dokumentu.
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

Tylko aktywator zdarzeń Cloud Firestore o zmianach w dokumencie. Aktualizacja dokumentu Cloud Firestore, w którym dane nie zmienia się (zapis no-op) nie generuje aktualizacji ani nie zapisuje zdarzenia. Jest nie można dodawać zdarzeń do określonych pól.

Jeśli nie masz jeszcze projektu, w którym włączono Cloud Functions dla Firebase, przeczytaj Pierwsze kroki z Cloud Functions dla Firebase (2 generacji) aby skonfigurować swój projekt Cloud Functions dla Firebase.

Jak pisać funkcje aktywowane 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 wzorzec wieloznaczny.

Wskaż pojedynczy dokument

Jeśli chcesz wywołać zdarzenie w przypadku dowolnej zmiany w określonym dokumencie: możesz użyć następującej 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 przy użyciu symboli wieloznacznych

Jeśli chcesz dołączyć regułę do grupy dokumentów, takich jak dowolny dokument w dla określonej kolekcji, a następnie użyj {wildcard} zamiast identyfikator 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, gdy zmieni się dowolne pole w dowolnym dokumencie w dokumencie users, zostanie ono dopasowane symbol wieloznaczny o nazwie userId.

Jeśli dokument w elemencie users ma kolekcje podrzędne i pole w jednej z tych podkolekcji dokument został zmieniony, symbol wieloznaczny userId nie zostanie wywołany.

Dopasowania zawierające symbole wieloznaczne są wyodrębniane ze ścieżki dokumentu i przechowywane w elemencie event.params. Możesz zdefiniować dowolną liczbę symboli wieloznacznych, aby zastąpić zbiór jawny identyfikatory dokumentów, 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 zbiorem. Jednak users/{userId}/{messageCollectionId}/{messageId} to prawidłowa, ponieważ {messageId} zawsze będzie wskazywać dokument.

Wyzwalacze zdarzeń

Aktywuj funkcję podczas tworzenia nowego dokumentu

Możesz aktywować tę funkcję za każdym razem, gdy w kolekcji zostanie utworzony nowy dokument. Ta przykładowa funkcja jest wyzwalana za każdym razem, gdy dodawany jest nowy profil 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, użyj 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 ...

Wywoływanie funkcji po zaktualizowaniu dokumentu

Możesz też wyzwalać tę 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, użyj 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ż uruchomić tę funkcję po usunięciu dokumentu. Ten przykład uruchamia się, 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, użyj 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 przy wszystkich zmianach w dokumencie

Jeśli nie interesuje Cię typ wywoływanego zdarzenia, możesz nasłuchiwać zmian w dokumencie Cloud Firestore przy użyciu „zapisanego dokumentu” wydarzenie . Ta przykładowa funkcja uruchamia się, gdy użytkownik zostanie utworzony, zaktualizowany lub usunięty:

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, użyj 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 wywołaniu funkcja udostępnia zrzut danych związanych z . Możesz użyć tego zrzutu do odczytu lub zapisu w dokumencie, który wywołało zdarzenie lub użyj pakietu Firebase Admin SDK, aby uzyskać dostęp do innych części. Twojej bazy danych.

Dane zdarzenia

Odczytywanie danych

Po wywołaniu funkcji warto pobrać dane z dokumentu, który została zaktualizowana, lub pobierz dane przed aktualizacją. Wcześniejsze dane możesz uzyskać, używając funkcji event.data.before, który zawiera zrzut dokumentu przed aktualizacją. Podobnie event.data.after zawiera stan zrzutu dokumentu po fragmencie .

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

Możesz uzyskać dostęp do właściwości tak samo jak w przypadku każdego innego obiektu. Ewentualnie może użyć funkcji get, aby uzyskać dostęp do określonych 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 (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 określonym dokumentem w tabeli Baza danych Cloud Firestore. Dostęp do tego dokumentu możesz uzyskać w zrzut zwrócony do funkcji.

Odniesienie do dokumentu obejmuje 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żytkowników

Jeśli używasz jednego z tych typów zdarzeń, masz dostęp do: informacje uwierzytelniające użytkownika dotyczące podmiotu zabezpieczeń, który wywołał zdarzenie. Są one uzupełnieniem 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 tutaj: Auth Context (Kontekst uwierzytelniania). Ten 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 poza zdarzeniem aktywującym

Funkcje w Cloud Functions są wykonywane w zaufanym środowisku. Są autoryzowane jako konto usługi w projekcie, a Ty możesz wykonywać odczyty zapisy za pomocą pakietu SDK Firebase Admin:

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

Zwróć uwagę na te ograniczenia aktywatorów Cloud Firestore dla Cloud Functions:

  • Cloud Functions (1 generacji) ma wymagania wstępne dla istniejących ustawień „(domyślnie)” w trybie natywnym Firestore. Nie obsługują nazwane bazy danych Cloud Firestore lub tryb Datastore. Użyj Cloud Functions (2 generacji), aby skonfigurować zdarzenia w takich przypadkach.
  • Zamówienie nie jest gwarantowane. Szybkie zmiany mogą aktywować wywołania funkcji w nieoczekiwanej kolejności.
  • Zdarzenia są wywoływane co najmniej raz, ale jedno zdarzenie może spowodować wiele wywołań funkcji. Unikaj zależnie od mechanika dokładnie raz, funkcji idempotentnych.
  • Cloud Firestore w trybie Datastore wymaga Cloud Functions (2 generacji). Cloud Functions (1 generacji) nie: obsługują tryb Datastore.
  • 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 żadnych jej aktywatorów. reguła przestaje dostarczać zdarzenia, ale będzie istnieć do momentu jej usunięcia;
  • Jeśli dopasowane zdarzenie przekracza maksymalny rozmiar żądania, w tagu 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 wliczają się do wykorzystania logów w projekcie.
    • Te logi znajdziesz w eksploratorze logów z komunikatem „Zdarzenie nie może dostarczyć do Funkcja w Cloud Functions z powodu przekroczenia limitu dla 1 generacji...” z error wagi. Nazwę funkcji znajdziesz pod polem functionName. Jeśli pole receiveTimestamp znajduje się jeszcze w ciągu godziny, możesz określić faktycznej treści wydarzenia, odczytując dany dokument z przed sygnaturą czasową i po niej.
    • Aby ich uniknąć:
      • Migracja i przejście na Cloud Functions (2 generacji)
      • Zmniejsz rozmiar dokumentu
      • Usuń te funkcje w Cloud Functions
    • Logowanie możesz wyłączyć przy użyciu wykluczeń. Pamiętaj jednak, że zdarzenia naruszające zasady nadal nie zostaną dostarczone.