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


С помощью 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 (2-го поколения) и 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)