Триггеры базы данных реального времени


С помощью Cloud Functions вы можете обрабатывать события в Firebase Realtime Database без необходимости обновления клиентского кода. Cloud Functions позволяет вам запускать операции Realtime Database с полными административными привилегиями и гарантирует, что каждое изменение в Realtime Database обрабатывается индивидуально. Вы можете вносить изменения Firebase Realtime Database через DataSnapshot или через Admin SDK .

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

  1. Ожидает изменений в определенном местоположении Realtime Database .
  2. Срабатывает при возникновении события и выполняет его задачи (примеры вариантов использования см. в разделе Что можно делать с помощью Cloud Functions ? ).
  3. Получает объект данных, содержащий моментальный снимок данных, хранящихся в указанном документе.

Запустить функцию Realtime Database

Создайте новые функции для событий Realtime Database с помощью functions.database . Чтобы контролировать, когда функция срабатывает, укажите один из обработчиков событий и укажите путь Realtime Database , где она будет прослушивать события.

Установить обработчик событий

Функции позволяют обрабатывать события Realtime Database на двух уровнях специфичности; вы можете прослушивать только события создания, обновления или удаления, или вы можете прослушивать любые изменения любого рода в пути. Cloud Functions поддерживает следующие обработчики событий для Realtime Database :

  • onWrite() , который срабатывает при создании, обновлении или удалении данных в Realtime Database .
  • onCreate() , который срабатывает при создании новых данных в Realtime Database .
  • onUpdate() , который срабатывает при обновлении данных в Realtime Database .
  • onDelete() , который срабатывает при удалении данных из Realtime Database .

Укажите экземпляр и путь

Чтобы контролировать, когда и где должна запускаться ваша функция, вызовите ref(path) для указания пути и, при необходимости, укажите экземпляр Realtime Database с помощью instance('INSTANCE_NAME') . Если вы не укажете экземпляр, функция будет развернута в экземпляре Realtime Database по умолчанию для проекта Firebase. Например:

  • Экземпляр Realtime Database по умолчанию: functions.database.ref('/foo/bar')
  • Экземпляр с именем "my-app-db-2": functions.database.instance('my-app-db-2').ref('/foo/bar')

Эти методы направляют вашу функцию на обработку записей по определенному пути в экземпляре Realtime Database . Спецификации пути соответствуют всем записям, которые касаются пути, включая записи, которые происходят где-либо ниже него. Если вы зададите путь для своей функции как /foo/bar , она будет соответствовать событиям в обоих этих местах:

 /foo/bar
 /foo/bar/baz/really/deep/path

В любом случае Firebase интерпретирует, что событие происходит в /foo/bar , а данные события включают старые и новые данные в /foo/bar . Если данные события могут быть большими, рассмотрите возможность использования нескольких функций на более глубоких путях вместо одной функции около корня вашей базы данных. Для лучшей производительности запрашивайте данные только на самом глубоком возможном уровне.

Вы можете указать компонент пути как подстановочный знак, окружив его фигурными скобками; ref('foo/{bar}') соответствует любому дочернему элементу /foo . Значения этих подстановочных компонентов пути доступны в объекте EventContext.params вашей функции. В этом примере значение доступно как context.params.bar .

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

{
  "foo": {
    "hello": "world",
    "firebase": "functions"
  }
}

соответствует пути "/foo/{bar}" дважды: один раз с "hello": "world" и еще раз с "firebase": "functions" .

Обработка данных событий

При обработке события Realtime Database возвращаемый объект данных — это DataSnapshot . Для событий onWrite или onUpdate первым параметром является объект Change , содержащий два снимка, представляющих состояние данных до и после события-триггера. Для событий onCreate и onDelete возвращаемый объект данных — это снимок созданных или удаленных данных.

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

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snapshot, context) => {
      // Grab the current value of what was written to the Realtime Database.
      const original = snapshot.val();
      functions.logger.log('Uppercasing', context.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing asynchronous tasks inside a Functions such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
      return snapshot.ref.parent.child('uppercase').set(uppercase);
    });

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

Из EventContext.auth и EventContext.authType вы можете получить доступ к пользовательской информации, включая разрешения, для пользователя, который запустил функцию. Это может быть полезно для обеспечения соблюдения правил безопасности, позволяя вашей функции выполнять различные операции в зависимости от уровня разрешений пользователя:

const functions = require('firebase-functions/v1');
const admin = require('firebase-admin');

exports.simpleDbFunction = functions.database.ref('/path')
    .onCreate((snap, context) => {
      if (context.authType === 'ADMIN') {
        // do something
      } else if (context.authType === 'USER') {
        console.log(snap.val(), 'written by', context.auth.uid);
      }
    });

Кроме того, вы можете использовать информацию аутентификации пользователя, чтобы «выдавать себя» за пользователя и выполнять операции записи от его имени. Обязательно удалите экземпляр приложения, как показано ниже, чтобы предотвратить проблемы с параллелизмом:

exports.impersonateMakeUpperCase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snap, context) => {
      const appOptions = JSON.parse(process.env.FIREBASE_CONFIG);
      appOptions.databaseAuthVariableOverride = context.auth;
      const app = admin.initializeApp(appOptions, 'app');
      const uppercase = snap.val().toUpperCase();
      const ref = snap.ref.parent.child('uppercase');

      const deleteApp = () => app.delete().catch(() => null);

      return app.database().ref(ref).set(uppercase).then(res => {
        // Deleting the app is necessary for preventing concurrency leaks
        return deleteApp().then(() => res);
      }).catch(err => {
        return deleteApp().then(() => Promise.reject(err));
      });
    });

Чтение предыдущего значения

Объект Change имеет свойство before , которое позволяет вам проверять, что было сохранено в Realtime Database до события. Свойство before возвращает DataSnapshot , где все методы (например, val() и exists() ) ссылаются на предыдущее значение. Вы можете снова прочитать новое значение, либо используя исходный DataSnapshot , либо прочитав свойство after . Это свойство любого Change является другим DataSnapshot , представляющим состояние данных после того, как произошло событие.

Например, свойство before можно использовать, чтобы убедиться, что функция при первом создании выводит текст только заглавными буквами:

exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onWrite((change, context) => {
      // Only edit data when it is first created.
      if (change.before.exists()) {
        return null;
      }
      // Exit when the data is deleted.
      if (!change.after.exists()) {
        return null;
      }
      // Grab the current value of what was written to the Realtime Database.
      const original = change.after.val();
      console.log('Uppercasing', context.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing asynchronous tasks inside a Functions such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
      return change.after.ref.parent.child('uppercase').set(uppercase);
    });