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


С помощью 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);
    });