Realtime Database トリガー


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 内のデータベース ノードの書き込み、作成、更新、削除に応じて、関数をトリガーできます。関数がトリガーされるタイミングを制御するには、イベント ハンドラの 1 つを指定し、イベントをリッスンする Realtime Database パスを指定します。

関数のロケーションを設定する

Realtime Database インスタンスのロケーションと関数のロケーションとの距離によっては、ネットワーク レイテンシが大幅に増加する可能性があります。また、リージョンが一致しない場合にはデプロイが失敗することもあります。このような状況を回避するには、データベース インスタンスのロケーションと一致するように関数のロケーションを指定してください。

Realtime Database イベントの処理

関数で Realtime Database イベントを処理するにあたり、リッスン対象とするイベントを 2 つのレベルで指定できます。そのレベルとは、「書き込み、作成、更新、削除の個々のイベントのみを対象とする」と「参照先のあらゆる変更を対象とする」です。

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 モジュールをインポートする必要があります。このサンプルでは、Realtime Database に書き込むために、HTTP モジュール、Realtime Database モジュール、Firebase Admin SDK モジュールをインポートする必要があります。

Node.js

// 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();

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/ の 1 レベル下のノード階層にあるすべての子と一致します。
  • アスタリスク(*)を正確に含むセグメントを使用します。たとえば、foo/app*-us は、foo/ の下にある子セグメントのうち、接頭辞が app であり、接尾辞が -us であるすべてのセグメントと一致します。

ワイルドカードを含むパスは、1 回の書き込みで複数のイベントに一致する場合があります。次のように挿入したとします。

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

これはパス "/foo/*" と 2 回一致します。1 回目は "hello": "world" で、2 回目は "firebase": "functions" です。

パスのキャプチャ。パスの一致を名前付き変数にキャプチャして、関数コード内で使用できます(例: /user/{uid}/user/{uid=*-us})。

キャプチャ変数の値は、関数の database.DatabaseEvent.params オブジェクト内で使用できます。

インスタンスのワイルドカード。ワイルドカードを使用してインスタンス コンポーネントを指定することもできます。インスタンスのワイルドカードには、接頭辞、接尾辞、またはその両方を含めることができます(例: my-app-*-prod)。

ワイルドカードとキャプチャのリファレンス

Cloud Functions(第 2 世代)と Realtime Database では、refinstance を指定するときにパターンを使用できます。各トリガー インターフェースには、関数のスコープ設定に関する次のオプションがあります。

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 プロパティがあり、作成イベントと削除イベントでは作成または削除されたデータのスナップショットが含まれます。

この例では、関数は参照されたパスのデータを取得し、その場所の文字列を大文字に変換して、変更された文字列をデータベースに書き込みます。

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 プロパティは、トリガーとなるイベントの前後のデータ状態を表す 2 つのスナップショットを含む Change オブジェクトです。Change オブジェクトには、イベントの発生前に Realtime Database に保存されていた内容を調べることができる before プロパティと、イベントの発生後のデータの状態を表す 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)