Триггеры Cloud Firestore


С помощью Cloud Functions вы можете обрабатывать события в Cloud Firestore без необходимости обновления клиентского кода. Изменения в Cloud Firestore можно вносить через интерфейс снимков документа или через Admin SDK .

В типичном жизненном цикле функция Cloud Firestore выполняет следующие действия:

  1. Ожидает изменений в определенном документе.
  2. Срабатывает при возникновении события и выполняет свои задачи.
  3. Получает объект данных, содержащий моментальный снимок данных, хранящихся в указанном документе. Для событий записи или обновления объект данных содержит два моментальных снимка, представляющих состояние данных до и после события-триггера.

Расстояние между экземпляром Firestore и местоположением функции может привести к значительной задержке в сети. Для оптимизации производительности рекомендуется указывать местоположение функции везде, где это применимо.

Триггеры функции Cloud Firestore

Пакет SDK Cloud Functions for Firebase экспортирует следующие триггеры событий Cloud Firestore, позволяя создавать обработчики, привязанные к определенным событиям Cloud Firestore:

Node.js

Тип события Курок
onDocumentCreated Срабатывает при первой записи в документ.
onDocumentUpdated Срабатывает, когда документ уже существует и в нем изменилось какое-либо значение.
onDocumentDeleted Срабатывает при удалении документа.
onDocumentWritten Срабатывает при срабатывании onDocumentCreated , onDocumentUpdated или onDocumentDeleted .
onDocumentCreatedWithAuthContext onDocumentCreated с дополнительной информацией об аутентификации
onDocumentWrittenWithAuthContext onDocumentWritten с дополнительной информацией об аутентификации
onDocumentDeletedWithAuthContext onDocumentDeleted с дополнительной информацией об аутентификации
onDocumentUpdatedWithAuthContext onDocumentUpdated с дополнительной информацией об аутентификации

Питон

Тип события Курок
on_document_created Срабатывает при первой записи в документ.
on_document_updated Срабатывает, когда документ уже существует и в нем изменилось какое-либо значение.
on_document_deleted Срабатывает при удалении документа.
on_document_written Срабатывает при срабатывании on_document_created , on_document_updated или on_document_deleted .
on_document_created_with_auth_context on_document_created с дополнительной информацией об аутентификации
on_document_updated_with_auth_context on_document_updated с дополнительной информацией об аутентификации
on_document_deleted_with_auth_context on_document_deleted с дополнительной информацией об аутентификации
on_document_written_with_auth_context on_document_written с дополнительной информацией об аутентификации

События Cloud Firestore срабатывают только при изменении документа. Обновление документа Cloud Firestore, данные в котором не изменялись (запись без операции), не генерирует событие обновления или записи. Невозможно добавлять события к определённым полям.

Если у вас еще нет проекта с поддержкой Cloud Functions for Firebase , прочтите статью Начало работы с Cloud Functions for Firebase (2-го поколения), чтобы настроить и оборудовать свой проект Cloud Functions for Firebase .

Написание функций, запускаемых Cloud Firestore

Определить триггер функции

Чтобы определить триггер Cloud Firestore, укажите путь к документу и тип события:

Node.js

import {
  onDocumentWritten,
  onDocumentCreated,
  onDocumentUpdated,
  onDocumentDeleted,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("my-collection/{docId}", (event) => {
   /* ... */ 
});

Питон

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:

Пути к документам могут ссылаться либо на конкретный документ , либо на подстановочный шаблон .

Укажите один документ

Если вы хотите инициировать событие для любого изменения в определенном документе, то вы можете использовать следующую функцию.

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("users/marie", (event) => {
  // Your code here
});

Питон

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:

Укажите группу документов с помощью подстановочных знаков

Если вы хотите прикрепить триггер к группе документов, например, к любому документу в определенной коллекции, то вместо идентификатора документа используйте {wildcard} :

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

Питон

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

В этом примере при изменении любого поля в документе users оно сопоставляется с подстановочным знаком userId .

Если документ в users имеет подколлекции и поле в одном из документов этих подколлекций изменяется, подстановочный знак userId не активируется.

Подстановочные знаки извлекаются из пути к документу и сохраняются в event.params . Вы можете определить любое количество подстановочных знаков для замены явных идентификаторов коллекций или документов, например:

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

Питон

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

Ваш триггер всегда должен указывать на документ, даже если вы используете подстановочный знак. Например, users/{userId}/{messageCollectionId} недопустим, поскольку {messageCollectionId} — это коллекция. Однако users/{userId}/{messageCollectionId}/{messageId} допустим, поскольку {messageId} всегда будет указывать на документ.

Триггеры событий

Запустить функцию при создании нового документа

Вы можете активировать функцию, которая будет срабатывать каждый раз при создании нового документа в коллекции. Этот пример функции срабатывает каждый раз при добавлении нового профиля пользователя:

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

Для получения дополнительной информации об аутентификации используйте onDocumentCreatedWithAuthContext .

Питон

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

Запустить функцию при обновлении документа

Вы также можете активировать функцию, которая будет срабатывать при обновлении документа. В этом примере функция срабатывает, если пользователь меняет свой профиль:

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

Для получения дополнительной информации об аутентификации используйте onDocumentUpdatedWithAuthContext .

Питон

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

Запустить функцию при удалении документа

Вы также можете активировать функцию при удалении документа. Эта функция срабатывает, когда пользователь удаляет свой профиль:

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

Для получения дополнительной информации об аутентификации используйте onDocumentDeletedWithAuthContext .

Питон

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

Запустить функцию для всех изменений в документе

Если тип события не важен, вы можете отслеживать все изменения в документе Cloud Firestore, используя триггер события «Document written». Этот пример функции срабатывает при создании, обновлении или удалении пользователя:

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

Для получения дополнительной информации об аутентификации используйте onDocumentWrittenWithAuthContext .

Питон

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

Чтение и запись данных

При срабатывании функции создается снимок данных, связанных с событием. Вы можете использовать этот снимок для чтения или записи данных в документ, вызвавший событие, или использовать Firebase Admin SDK для доступа к другим частям базы данных.

Данные о событиях

Чтение данных

При срабатывании функции может потребоваться получить данные из документа, который был обновлён, или данные до обновления. Получить предыдущие данные можно с помощью event.data.before , который содержит снимок документа до обновления. Аналогично, event.data.after содержит снимок состояния документа после обновления.

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

Питон

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

Вы можете получить доступ к свойствам так же, как к любому другому объекту. Кроме того, для доступа к определённым полям можно использовать функцию 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');

Питон

# 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"]

Запись данных

Каждый вызов функции связан с определённым документом в базе данных Cloud Firestore. Вы можете получить доступ к этому документу в снимке состояния, возвращаемом функцией.

Ссылка на документ включает такие методы, как update() , set() и remove() чтобы вы могли изменить документ, вызвавший эту функцию.

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

});

Питон

@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})

Доступ к информации об аутентификации пользователя

При использовании одного из следующих типов событий вы можете получить доступ к информации об аутентификации пользователя, инициировавшего событие. Эта информация дополняет информацию, возвращаемую в базовом событии.

Node.js

  • onDocumentCreatedWithAuthContext
  • onDocumentWrittenWithAuthContext
  • onDocumentDeletedWithAuthContext
  • onDocumentUpdatedWithAuthContext

Питон

  • on_document_created_with_auth_context
  • on_document_updated_with_auth_context
  • on_document_deleted_with_auth_context
  • on_document_written_with_auth_context

Информацию о данных, доступных в контексте аутентификации, см. в разделе Контекст аутентификации . В следующем примере показано, как получить информацию об аутентификации:

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

Питон

@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

Данные за пределами события-триггера

Cloud Functions выполняются в доверенной среде. Они авторизованы как сервисная учётная запись в вашем проекте, и вы можете выполнять чтение и запись с помощью 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
    });
  });

Питон

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({
      # ...
  })

Ограничения

Обратите внимание на следующие ограничения для триггеров Cloud Firestore для Cloud Functions:

  • Для работы Cloud Functions (1-го поколения) требуется существующая база данных (default) в собственном режиме Firestore. Она не поддерживает именованные базы данных Cloud Firestore или режим Datastore. В таких случаях для настройки событий используйте Cloud Functions (2-го поколения).
  • Порядок не гарантируется. Быстрые изменения могут привести к вызову функций в неожиданном порядке.
  • События доставляются как минимум один раз, но одно событие может привести к нескольким вызовам функций. Избегайте зависимости от механики «точно один раз» и пишите идемпотентные функции .
  • Для работы Cloud Firestore в режиме хранилища данных требуется Cloud Functions (2-го поколения). Cloud Functions (1-го поколения) не поддерживает режим хранилища данных.
  • Триггер связан с одной базой данных. Вы не можете создать триггер, соответствующий нескольким базам данных.
  • Удаление базы данных не приводит к автоматическому удалению всех триггеров для этой базы данных. Триггер перестаёт отправлять события, но продолжает существовать до тех пор, пока вы его не удалите .
  • Если размер сопоставленного события превышает максимальный размер запроса , событие может быть не доставлено в Cloud Functions (1-го поколения).
    • События, не доставленные из-за размера запроса, регистрируются в журналах платформы и учитываются при расчете использования журнала для проекта.
    • Эти журналы можно найти в обозревателе журналов с сообщением «Событие не может быть доставлено в облачную функцию из-за превышения лимита размера для 1-го поколения...» с указанием уровня серьёзности error . Имя функции можно найти в поле functionName . Если значение поля receiveTimestamp всё ещё не истекло в течение часа, вы можете определить фактическое содержимое события, прочитав соответствующий документ со снимком до и после временной метки.
    • Чтобы избежать такой каденции, вы можете:
      • Миграция и обновление до Cloud Functions (2-го поколения)
      • Уменьшить размер документа
      • Удалить соответствующие Cloud Functions
    • Вы можете отключить ведение журнала с помощью исключений , но учтите, что нежелательные события все равно не будут доставлены.