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


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

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

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

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

Установка местоположения функции

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

Обработка событий Realtime Database

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

Доступны следующие обработчики для реагирования на события Realtime Database :

  • onValueWritten() Срабатывает при создании, обновлении или удалении данных в Realtime Database .
  • onValueCreated() Срабатывает только при создании данных в Realtime Database .
  • onValueUpdated() Срабатывает только при обновлении данных в Realtime Database .
  • onValueDeleted() Срабатывает только при удалении данных в Realtime Database .
  • on_value_written() Срабатывает при создании, обновлении или удалении данных в Realtime Database .
  • on_value_created() Срабатывает только при создании данных в Realtime Database .
  • on_value_updated() Срабатывает только при обновлении данных в Realtime Database .
  • on_value_deleted() Срабатывает только при удалении данных в Realtime Database .

Импортировать необходимые модули

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

// The Cloud Functions for Firebase SDK to setup triggers and logging.
const {onRequest} = require("firebase-functions/v2/https");
const {onValueCreated} = require("firebase-functions/v2/database");
const {logger} = require("firebase-functions");

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require("firebase-admin");
admin.initializeApp();
# The Cloud Functions for Firebase SDK to create Cloud Functions and set up triggers.
from firebase_functions import db_fn, https_fn

# The Firebase Admin SDK to access the Firebase Realtime Database.
from firebase_admin import initialize_app, db

app = initialize_app()

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

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

Например:

// All Realtime Database instances in default function region us-central1 at path "/user/{uid}"
// There must be at least one Realtime Database present in us-central1.
const onWrittenFunctionDefault = onValueWritten("/user/{uid}", (event) => {
  // …
});

// Instance named "my-app-db-2", at path "/user/{uid}".
// The "my-app-db-2" instance must exist in this region.
const OnWrittenFunctionInstance = onValueWritten(
  {
    ref: "/user/{uid}",
    instance: "my-app-db-2"
    // This example assumes us-central1, but to set location:
    // region: "europe-west1"
  },
  (event) => {
    // …
  }
);

// Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com.
// There must be at least one Realtime Database with "my-app-db-*" prefix in this region.
const onWrittenFunctionInstance = onValueWritten(
  {
    ref: "/user/{uid=*@gmail.com}",
    instance: "my-app-db-*"
    // This example assumes us-central1, but to set location:
    // region: "europe-west1"
  },
  (event) => {
    // …
  }
);
# All Realtime Database instances in default function region us-central1 at path "/user/{uid}"
# There must be at least one Realtime Database present in us-central1.
@db_fn.on_value_written(r"/user/{uid}")
def onwrittenfunctiondefault(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

# Instance named "my-app-db-2", at path "/user/{uid}".
# The "my-app-db-2" instance must exist in this region.
@db_fn.on_value_written(
    reference=r"/user/{uid}",
    instance="my-app-db-2",
    # This example assumes us-central1, but to set location:
    # region="europe-west1",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

# Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com.
# There must be at least one Realtime Database with "my-app-db-*" prefix in this region.
@db_fn.on_value_written(
    reference=r"/user/{uid=*@gmail.com}",
    instance="my-app-db-*",
    # This example assumes us-central1, but to set location:
    # region="europe-west1",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

Эти параметры указывают вашей функции на необходимость обработки записей по определенному пути в экземпляре Realtime Database .

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

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

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

Подстановка и захват

Вы можете использовать {key} , {key=*} , {key=prefix*} , {key=*suffix} для захвата. * , prefix* , *suffix для односегментного подстановочного знака. Примечание: ** представляет многосегментный подстановочный знак, который Realtime Database не поддерживает. См. Понимание шаблонов пути .

Подстановка подстановочных знаков пути. Вы можете указать компонент пути как подстановочный знак:

  • Используя звездочку, * . Например, foo/* соответствует любым дочерним элементам на один уровень иерархии узлов ниже foo/ .
  • Использование сегмента, содержащего только звездочку, * . Например, foo/app*-us соответствует любым дочерним сегментам ниже foo/ с префиксом app и суффиксом -us .

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

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

сопоставляет путь "/foo/*" дважды: один раз с "hello": "world" и еще раз с "firebase": "functions" .

Захват пути. Вы можете захватить соответствия пути в именованные переменные для использования в коде вашей функции (например, /user/{uid} , /user/{uid=*-us} ).

Значения переменных захвата доступны в объекте database.DatabaseEvent.params вашей функции.

Подстановочные знаки экземпляра. Вы также можете указать компонент экземпляра с помощью подстановочных знаков. Подстановочный знак экземпляра может иметь префикс, суффикс или и то, и другое (например, my-app-*-prod ).

Ссылка на подстановочный знак и захват

С Cloud Functions (2nd gen) и Realtime Database шаблон может использоваться при указании ref и instance . Каждый интерфейс триггера будет иметь следующие параметры для определения области действия функции:

Указание ref Указание instance Поведение
Один ( /foo/bar ) Не уточняется Обрабатывает обработчик всех экземпляров в области функции.
Один ( /foo/bar ) Один ( 'my-new-db' ) Обрабатывает обработчик в пределах конкретного экземпляра в области функций.
Один ( /foo/bar ) Шаблон ( 'inst-prefix*' ) Обрабатывает обработчик всех экземпляров, соответствующих шаблону в области функции.
Шаблон ( /foo/{bar} ) Не уточняется Обрабатывает обработчик всех экземпляров в области функции.
Шаблон ( /foo/{bar} ) Один ( 'my-new-db' ) Обрабатывает обработчик в пределах конкретного экземпляра в области функций.
Шаблон ( /foo/{bar} ) Шаблон ( 'inst-prefix*' ) Обрабатывает обработчик всех экземпляров, соответствующих шаблону в области функции.

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

Когда событие Realtime Database срабатывает, оно передает объект Event в вашу функцию-обработчик. Этот объект имеет свойство data , которое для событий создания и удаления содержит снимок созданных или удаленных данных.

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

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
// for all databases in 'us-central1'
exports.makeuppercase = onValueCreated(
    "/messages/{pushId}/original",
    (event) => {
    // Grab the current value of what was written to the Realtime Database.
      const original = event.data.val();
      logger.log("Uppercasing", event.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing
      // asynchronous tasks inside a function, such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the
      // Realtime Database returns a Promise.
      return event.data.ref.parent.child("uppercase").set(uppercase);
    },
);
@db_fn.on_value_created(reference="/messages/{pushId}/original")
def makeuppercase(event: db_fn.Event[Any]) -> None:
    """Listens for new messages added to /messages/{pushId}/original and
    creates an uppercase version of the message to /messages/{pushId}/uppercase
    """

    # Grab the value that was written to the Realtime Database.
    original = event.data
    if not isinstance(original, str):
        print(f"Not a string: {event.reference}")
        return

    # Use the Admin SDK to set an "uppercase" sibling.
    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent = db.reference(event.reference).parent
    if parent is None:
        print("Message can't be root node.")
        return
    parent.child("uppercase").set(upper)

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

Для событий write или update свойство data — это объект Change , содержащий два снимка, представляющих состояние данных до и после события-триггера. Объект Change имеет свойство before , которое позволяет вам проверять, что было сохранено в Realtime Database до события, и свойство after , которое представляет состояние данных после того, как событие произошло.

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

  exports makeUppercase = onValueWritten("/messages/{pushId}/original", (event) => {
        // Only edit data when it is first created.
        if (event.data.before.exists()) {
          return null;
        }
        // Exit when the data is deleted.
        if (!event.data.after.exists()) {
          return null;
        }
        // Grab the current value of what was written to the Realtime Database.
        const original = event.data.after.val();
        console.log('Uppercasing', event.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 event.data.after.ref.parent.child('uppercase').set(uppercase);
      });
@db_fn.on_value_written(reference="/messages/{pushId}/original")
def makeuppercase2(event: db_fn.Event[db_fn.Change]) -> None:
    """Listens for new messages added to /messages/{pushId}/original and
    creates an uppercase version of the message to /messages/{pushId}/uppercase
    """

    # Only edit data when it is first created.
    if event.data.before is not None:
        return

    # Exit when the data is deleted.
    if event.data.after is None:
        return

    # Grab the value that was written to the Realtime Database.
    original = event.data.after
    if not hasattr(original, "upper"):
        print(f"Not a string: {event.reference}")
        return

    # Use the Admin SDK to set an "uppercase" sibling.
    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent = db.reference(event.reference).parent
    if parent is None:
        print("Message can't be root node.")
        return
    parent.child("uppercase").set(upper)