y-ohgi's blog

TODO: ここになにかかく

Firebase Realtime Databaseの更新をCloud Functionsで検知する

TL;DR

  • FirebaseのRealtime DatabaseのイベントをCloud Functions for Firebaseでキャッチする
  • Realtime Databaseの特定のパスのデータの変更内容を検知する

About

Firebase Realtime Database(以下DB)内で発火されたイベントを取得し、変更内容を確認したいことが動機です。
Cloud Functions for Firebase(以下Function)公式ドキュメントで提示されているトリガーで、実際にGCPのログエクスプローラでの確認まで行います。

Realtime Database トリガー  |  Cloud Functions for Firebase

version

  • firebase (cli)
    • 13.7.3
  • node
    • 20

Code

github.com

How to

プロジェクトのauth

FirebaseはBlazeプランを選択し、認証を行います。

gcloud auth login
gcloud auth application-default login

firebase login <email>

Firebaseのローカル環境を作成

Firebaseのデプロイ環境を作成します。

$ firebase init

以下の機能の使用を選択 - Realtime Database - Functions - Emulators - 任意

? Please select an option に対して、既存のプロジェクトの使用を選択。
> Use an existing project

Realtime Databaseを作成していない場合、以下の問いが表示されるのでY(es)。
? It seems like you haven’t initialized Realtime Database in your project yet. Do you want to set it up? (Y/n)

その後どのリージョンにDBのインスタンスを配置するか聞かれるので、今回は日本に近いシンガポールリージョンを選択。

? Please choose the location for your default Realtime Database instance:
  us-central1
  europe-west1
> asia-southeast1

その後はDBのルール・言語・エミュレータの使用などを問われるので、TypeScriptを選択する以外は適宜。

DBへダミーデータを挿入

users/ パス配下に適当なユーザーオブジェクトを作成。

e.g.: users/ 配下に one , two というオブジェクトを追加するコード。
https://github.com/y-ohgi/blog-firebase-db-event/blob/main/scripts/scr/write.ts

const userRef = db.ref('users');

(async () => {
  await userRef.set({
    one: {
      name: faker.internet.userName(),
      bio: faker.lorem.lines()
    },
    two: {
      name: faker.internet.userName(),
      bio: faker.lorem.lines()
    }
  })
    .then(() => {
      console.log('done')
    })
    .catch(console.error)

  process.exit(0)
})();

以下のようにダミーデータを挿入できていれば成功。

リスナーFunctionを作成

DBの /users/{uid} 配下の変更を検知するFunctionを作成します。

$ firebase init で作成された functions/src/index.ts がFunctionのコードになります。
onValueWritten を使用して全てのイベントに対して発火するようにします。
https://github.com/y-ohgi/blog-firebase-db-event/blob/main/functions/src/index.ts

import * as admin from "firebase-admin";
import * as logger from "firebase-functions/logger";
import {setGlobalOptions} from "firebase-functions/v2";
import {onValueWritten} from "firebase-functions/v2/database";

admin.initializeApp();

// MEMO:
//   Realtime Databaseと同じリージョンで実行したいため、asia-southeast1を指定。
//   また、CPU/Memoryなどの設定もここで指定可能。
setGlobalOptions({
  region: "asia-southeast1",
});

// "/users/{uid}" 配下で発生したイベントをリッスン
const onUserWritten = onValueWritten("users/{uid}", (event) => {
  logger.log(event);
});

export = {onUserWritten}

以下のコマンドでFunctionをデプロイ。

$ firebase deploy --only functions -P <YOUR PROJECT>

NIT:
GCPのEventarcサービスを使用するため、Eventarcサービスエージェントが作成されていない場合失敗する可能性があります(1敗)。
サービスエージェントの更新を待ち(5分ほど)、再度デプロイすると成功する可能性があります。

ログの確認

デプロイが完了するとFunctionsのダッシュボードに関数が追加されています。

ログを表示 、からログエクスプローラへ移り、Realtime Databaseを更新しイベントが発火されていることを確認します。

所感

指定したパス配下(e.g. /users/{uid} )の更新を無邪気にトリガーしてくれるので、容量用法を守って使用したい次第です。
また、Eventarcの対応幅に圧倒されました。なんでもできますねEventarc。apigeeeまで対応しているのは流石にびっくりです。