Realtime Database トリガー

一般的なライフサイクルの場合、Firebase Realtime Database の関数は以下のように機能します。

  1. 特定の Realtime Database のパスに変更が加えられるのを待ちます。
  2. イベントが発生するとトリガーされ、そのタスクを実行します。
  3. そのパスに保存されているデータのスナップショットを含むデータ オブジェクトを受け取ります。

Firebase Realtime Database 内のデータベース ノードの書き込み、作成、更新、削除に応じて、関数をトリガーできます。

Firebase Realtime Database の変更で関数をトリガーする

firebase-functions/v2/database サブパッケージを使用して、Firebase Realtime Database イベントを処理する関数を作成します。関数がトリガーされるタイミングを制御するには、イベント ハンドラを 1 つ指定し、イベントをリッスンする Realtime Database パスを指定します。

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

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

Realtime Database イベントの処理

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

Realtime Database イベントに応答するハンドラは次のとおりです。

  • onValueWritten() Realtime Database 内でデータが書き込まれた場合にのみトリガーされます。
  • onValueCreated() Realtime Database 内でデータが作成された場合にのみトリガーされます。
  • onValueUpdated() Realtime Database 内でデータが更新された場合にのみトリガーされます。
  • onValueDeleted() Realtime Database 内でデータが削除された場合にのみトリガーされます。

インスタンスとパスを指定する

関数がトリガーされるタイミングと場所を制御するには、関数をパスで構成し、必要に応じて Realtime Database インスタンスを指定します。インスタンスを指定しない場合、関数は、その関数があるリージョン内のすべての Realtime Database インスタンスにデプロイされます。Realtime Database インスタンスのパターンを指定して、同じリージョン内のインスタンスのサブセットにデプロイすることもできます。

たとえば、onValueWritten() を使用する場合は以下のようになります。

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

これらのパラメータは、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 を使用できます。注: ** は、RTDB では対応していないマルチセグメントのワイルドカードを表します。パスパターンについてをご覧ください。

パスのワイルドカード。パス コンポーネントをワイルドカードとして指定できます。

  • アスタリスク(*)を使用します。たとえば、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 イベントを処理するときに返されるデータ オブジェクトは DataSnapshot です。

onValueWritten イベントまたは onValueUpdated イベントの場合、最初のパラメータは、トリガーとなるイベントの前後のデータ状態を表す 2 つのスナップショットを含む Change オブジェクトです。

onValueCreated イベントと onValueDeleted イベントの場合、返されるデータ オブジェクトは、作成または削除されたデータのスナップショットです。

この例では関数は指定されたパス foo/bar のスナップショットを snap として取得し、その場所の文字列を大文字に変換して、変更された文字列をデータベースに書き込みます。

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
export makeuppercase = onValueCreated("foo/bar", (event) => {
      // Grab the current value of what was written to the Realtime Database.
      const original = event.data.val();
      functions.logger.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.ref.parent.child('uppercase').set(uppercase);
    });

前の値を読み取る

Change オブジェクトの before プロパティを使用すると、イベントの発生前に Realtime Database に保存されていた内容を調べることができます。before プロパティによって返される DataSnapshot では、すべてのメソッド(val()exists() など)が前の値を参照します。新しい値を再度読み取るには、元の DataSnapshot を使用するか、after プロパティを読み取ります。Change のこのプロパティによって返される DataSnapshot は、イベント発生後のデータの状態を表します。

たとえば、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);
        });