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

С помощью 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 доступны следующие обработчики:

Node.js

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

Python

  • Функция 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 .

Node.js

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

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require("firebase-admin");
admin.initializeApp();

Python

# 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 для развертывания на выборочном подмножестве экземпляров в том же регионе.

Например:

Node.js

// 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) => {
    // …
  }
);

Python

# 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 Поведение
Single ( /foo/bar ) Не указано Обработчик области действия распространяется на все экземпляры в области действия функции.
Single ( /foo/bar ) Single ( 'my-new-db' ) Обработчик области видимости привязан к конкретному экземпляру в области функции.
Single ( /foo/bar ) Шаблон ( 'inst-prefix*' ) Обработчик области действия применяется ко всем экземплярам, ​​соответствующим шаблону в области функции.
Шаблон ( /foo/{bar} ) Не указано Обработчик области действия распространяется на все экземпляры в области действия функции.
Шаблон ( /foo/{bar} ) Single ( 'my-new-db' ) Обработчик области видимости привязан к конкретному экземпляру в области функции.
Шаблон ( /foo/{bar} ) Шаблон ( 'inst-prefix*' ) Обработчик области действия применяется ко всем экземплярам, ​​соответствующим шаблону в области функции.

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

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

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

Node.js

// 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);
    },
);

Python

@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 можно использовать для того, чтобы функция преобразовывала текст в верхний регистр только при первом создании:

Node.js

  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);
      });

Python

@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)