Триггеры Cloud Firestore (первое поколение)

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

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

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

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

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

Cloud Functions for Firebase SDK экспортирует объект functions.firestore, который позволяет создавать обработчики, связанные с определенными событиями Cloud Firestore.

Тип события Триггер
onCreate Срабатывает при первой записи в документ.
onUpdate Срабатывает, когда документ уже существует и в нем изменяется какое-либо значение.
onDelete Срабатывает при удалении документа с данными.
onWrite Срабатывает, когда срабатывает onCreate, onUpdate или onDelete.

Если у вас ещё нет проекта, в котором включена функция Cloud Functions for Firebase, прочитайте статью Начало работы: напишите и разверните свои первые функции, чтобы настроить проект Cloud Functions for Firebase.

Как писать функции, запускаемые Cloud Firestore

Как задать триггер функции

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

Node.js

const functions = require('firebase-functions');

exports.myFunction = functions.firestore
  .document('my-collection/{docId}')
  .onWrite((change, context) => { /* ... */ });

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

Как указать один документ

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

Node.js

// Listen for any change on document `marie` in collection `users`
exports.myFunctionName = functions.firestore
    .document('users/marie').onWrite((change, context) => {
      // ... Your code here
    });

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

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

Node.js

// Listen for changes in all documents in the 'users' collection
exports.useWildcard = functions.firestore
    .document('users/{userId}')
    .onWrite((change, context) => {
      // If we set `/users/marie` to {name: "Marie"} then
      // context.params.userId == "marie"
      // ... and ...
      // change.after.data() == {name: "Marie"}
    });

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

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

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

Node.js

// Listen for changes in all documents in the 'users' collection and all subcollections
exports.useMultipleWildcards = functions.firestore
    .document('users/{userId}/{messageCollectionId}/{messageId}')
    .onWrite((change, context) => {
      // If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
      // context.params.userId == "marie";
      // context.params.messageCollectionId == "incoming_messages";
      // context.params.messageId == "134";
      // ... and ...
      // change.after.data() == {body: "Hello"}
    });

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

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

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

Node.js

exports.createUser = functions.firestore
    .document('users/{userId}')
    .onCreate((snap, context) => {
      // Get an object representing the document
      // e.g. {'name': 'Marie', 'age': 66}
      const newValue = snap.data();

      // access a particular field as you would any JS property
      const name = newValue.name;

      // perform desired operations ...
    });

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

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

Node.js

exports.updateUser = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
      // Get an object representing the document
      // e.g. {'name': 'Marie', 'age': 66}
      const newValue = change.after.data();

      // ...or the previous value before this update
      const previousValue = change.before.data();

      // access a particular field as you would any JS property
      const name = newValue.name;

      // perform desired operations ...
    });

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

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

Node.js

exports.deleteUser = functions.firestore
    .document('users/{userID}')
    .onDelete((snap, context) => {
      // Get an object representing the document prior to deletion
      // e.g. {'name': 'Marie', 'age': 66}
      const deletedValue = snap.data();

      // perform desired operations ...
    });

Как активировать функцию при любых изменениях в документе

Если вам не важен тип события, вы можете отслеживать все изменения в документе Cloud Firestore с помощью функции onWrite() с подстановочным знаком. В этом примере функция вызывает modifyUser, если пользователь создан, обновлен или удален:

Node.js

exports.modifyUser = functions.firestore
    .document('users/{userID}')
    .onWrite((change, context) => {
      // Get an object with the current document value.
      // If the document does not exist, it has been deleted.
      const document = change.after.exists ? change.after.data() : null;

      // Get an object with the previous document value (for update or delete)
      const oldDocument = change.before.data();

      // perform desired operations ...
    });

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

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

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

Данные о чтении

Когда функция активируется, вам может понадобиться получить данные из обновленного документа или данные до обновления. Чтобы получить предыдущие данные, используйте параметр change.before.data(), который содержит снимок документа до обновления. Аналогично, change.after.data() содержит состояние снимка документа после обновления.

Node.js

exports.updateUser2 = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
      // Get an object representing the current document
      const newValue = change.after.data();

      // ...or the previous value before this update
      const previousValue = change.before.data();
    });

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

Node.js

// Fetch data using standard accessors
const age = snap.data().age;
const name = snap.data()['name'];

// Fetch data using built in accessor
const experience = snap.get('experience');

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

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

Этот объект DocumentReference поступает из Cloud Firestore Node.js SDK и включает такие методы, как update(), set() и remove(), чтобы вы могли легко изменить документ, который вызвал функцию.

Node.js

// Listen for updates to any `user` document.
exports.countNameChanges = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
      // Retrieve the current and previous value
      const data = change.after.data();
      const previousData = change.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 change.after.ref.set({
        name_change_count: count + 1
      }, {merge: true});
    });

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

Cloud Functions выполняются в доверенной среде, то есть авторизованы как сервисный аккаунт в вашем проекте. Вы можете выполнять операции чтения и записи с помощью Firebase Admin SDK:

Node.js

const admin = require('firebase-admin');
admin.initializeApp();

const db = admin.firestore();

exports.writeToFirestore = functions.firestore
  .document('some/doc')
  .onWrite((change, context) => {
    db.doc('some/otherdoc').set({ ... });
  });

Ограничения

Обратите внимание на следующие ограничения для триггеров Cloud Firestore для облачных функций:

  • Для Cloud Functions (первого поколения) требуется существующая база данных "(default)" в стандартном режиме Firestore. Он не поддерживает базы данных с именами Cloud Firestore и режим хранилища данных. В таких случаях для настройки событий используйте Cloud Functions (второе поколение).
  • Настройка между проектами с триггерами Cloud Functions и Cloud Firestore не поддерживается. Чтобы настроить триггер Cloud Firestore, триггер Cloud Functions должен находиться в том же проекте.
  • Порядок не гарантируется. Быстрые изменения могут привести к тому, что функции будут вызываться в неожиданном порядке.
  • События доставляются хотя бы один раз, но одно событие может привести к нескольким вызовам функции. Не полагайтесь на механизмы, обеспечивающие доставку ровно один раз, и пишите идемпотентные функции.
  • Cloud Firestore в режиме хранилища данных требует Cloud Functions (второе поколение). Cloud Functions первого поколения не поддерживает режим Datastore.
  • Триггер связан с одной базой данных. Нельзя создать триггер, который соответствует нескольким базам данных.
  • При удалении базы данных триггеры для нее не удаляются автоматически. Триггер перестанет активировать события, но будет существовать, пока вы не удалите его.
  • Если размер подходящего события превышает максимальный размер запроса, событие может не быть доставлено в Cloud Functions (первое поколение).
    • События, не доставленные из-за размера запроса, регистрируются в журналах платформы и учитываются при подсчете использования журналов для проекта.
    • Эти журналы можно найти в проводнике журналов с сообщением "Event cannot deliver to Cloud function due to size exceeding the limit for 1st gen..." errorуровня серьезности. Название функции можно найти в поле functionName. Если значение поля receiveTimestamp находится в пределах часа от текущего времени, вы можете определить фактическое содержимое события, прочитав документ, о котором идет речь, с помощью моментального снимка до и после временной метки.
    • Чтобы избежать этого, вы можете:
      • Переход на Nest Protect Cloud Functions 2-го поколения
      • Как уменьшить размер документа
      • Удалите Cloud Functions.
    • Вы можете отключить ведение журнала с помощью исключений, но учтите, что события, нарушающие правила, по-прежнему не будут доставляться.