Cloud Functions への移行に向けて Firebase Extensions を準備する

このガイドでは、非推奨の Firebase Extensions 環境から、ユーザーが独自の Cloud Functions にインストールしてデプロイする Firebase(第 2 世代)コードベースの関数に拡張機能を移行する方法について説明します。

これは推奨される移行パスです。Firebase は、公式の npm 相当の拡張機能のリストを保持します。このガイドでは、独自の拡張機能を作成する手順について説明します。

このガイドでは、Firestore から BigQuery へのストリーミング拡張機能(firestore-bigquery-export)を例として使用します。各セクションの最後には、移行前の拡張機能と、@firebase/firestore-bigquery-export パッケージとしての移行後の拡張機能の例を示すが記載されています。

登録して、拡張機能の移行に関する詳細情報とサポートを入手する

Firebase Extensions からの移行方法についてご不明な点がある場合は、firebase-extensions-migrator-support-external@google.com までお問い合わせください。また、第 2 世代関数のパッケージ化、テスト、配布に関する詳細情報をガイドに追加する際にも、このグループにメールでお知らせします。

このグループに参加するには、firebase-extensions-migrator-support-external+subscribe@google.com にメッセージを送信します。メンバーシップ リクエストのメールが返信されます。そのメールに返信する必要があります。[Join This Group] ボタンをクリックしないでください。

始める前に

この移行を完了するには、Cloud Functions の次の機能を使用します。

  • パラメータ化された構成extension.yaml で宣言した各パラメータは、パッケージ コードで定義されたパラメータになります。

  • 宣言型 IAM ロールと必要な API。extension.yaml で宣言した各ロールは requiresRole(...) 呼び出しになり、各 API は関数コード内の requiresAPI(...) 呼び出しになります。デプロイ時に、Firebase CLI は宣言されたロールをマネージド ランタイム サービス アカウントに付与し、宣言された API をユーザーに代わって有効にします。

  • Cloud Functions コードベースのライフサイクル イベント。Cloud Functions コードベースで、Firebase Extensions と同様のライフサイクル イベントがサポートされるようになりました。ライフサイクル フック afterFirstDeploy(...)afterRedeploy(...) を使用して、インストール時と更新時の設定を宣言します。これらは、extension.yaml で宣言した lifecycleEvents に代わるものです。

拡張機能のインベントリを作成する

まず、拡張機能のインベントリを作成します。これは、拡張機能が宣言、出荷、ドキュメント化するすべてのものの完全なリストです。これにより、すべての動作が第 2 世代の関数で定義された宛先を持ち、移行で失われるものがなくなります。

以下の各項目を確認し、見つかった内容をメモします。

  • extension.yaml: パラメータ、関数、イベント、IAM ロール、必要な API、シークレット、ライフサイクル フックを宣言します。

  • functions/: 関数コード、依存関係、ビルド構成、トリガー、タスクキュー関数が含まれます。

  • README.mdPREINSTALL.mdPOSTINSTALL.md。設定手順、警告、課金に関するメモが含まれています。

  • scripts/。インポート、バックフィル、IAM、修復、移行ユーティリティ、および拡張機能とともに提供するその他のツールが含まれます。

次に、extension.yaml の各アイテムについて、npm パッケージのどこに配置するかを決定します。

  • ユーザー構成を Cloud Functions パラメータに変換します(セクション 5)。

  • シークレットを Cloud Functions シークレットに変換します(セクション 6)。

  • IAM ロールを requiresRole(...) 宣言に変換します(セクション 8)。

  • 必要な Google API を適切な requiresAPI(...) 宣言に変換します(セクション 8)。

  • インストール フックと更新フックを afterFirstDeploy(...) 宣言と afterRedeploy(...) 宣言に変換します(セクション 8)。

実施例: Firestore を BigQuery にストリーミングする

firestore-bigquery-export/extension.yaml と functions/ を読み取ると、次のインベントリが生成されます。

extension.yaml 内 カウント / 値 データの送信先
params 25(COLLECTION_PATH、DATASET_ID、TABLE_ID、DATASET_LOCATION、VIEW_TYPE、…) Cloud Functions パラメータ(セクション 5)
API bigquery.googleapis.com requiresAPI(...)(セクション 7)
ロール bigquery.dataEditor、datastore.user、bigquery.user requiresRole(...)(セクション 7)
リソース 1 つのイベント トリガー(fsexportbigquery)+ タスクキュー関数(initBigQuerySync、setupBigQuerySync) エクスポートされたパッケージ関数(セクション 3)
lifecycleEvents onInstall → initBigQuerySync、onUpdate / onConfigure → setupBigQuerySync afterFirstDeploy / afterRedeploy(セクション 9)
scripts/ import/(バックフィル)、gen-schema-view/ スクリプトとして保持(ここでは対象外)

この拡張機能はシークレット パラメータの型を宣言していないため、このガイドのセクション 6 で移行するものは何もありません。イベント トリガーはすでに第 2 世代です。タスクキュー関数のみが第 1 世代のままです(セクション 3 を参照)。

package.json を更新する

拡張機能の package.json ファイルを更新します。1 つの拡張機能を移行する場合は、ルート package.json になります。1 つのリポジトリで多くの拡張機能を移行する場合は、各拡張機能に独自のパッケージを指定します。

最小 SDK バージョン。firebase-functions >= 7.3 と firebase-admin >= 14.2.0 を依存関係として宣言します。firebase-functions のバージョンもピア依存関係として宣言する

{
  "name": "<package-name>",
  "version": "1.0.0",
  "main": "lib/index.js",
  "types": "lib/index.d.ts",
  "exports": {
    ".": {
      "types": "./lib/index.d.ts",
      "default": "./lib/index.js"
    }
  },
  "engines": {
    "node": ">=22"
  },
  "peerDependencies": {
    "firebase-functions": "^7.3.0"
  },
  "dependencies": {
    "firebase-functions": "^7.3.0",
    "firebase-admin": "^14.2.0"
  }
}

通常の依存関係に加えて firebase-functions をピア依存関係として宣言し、ユーザーの Cloud Functions プロジェクトにライブラリの作成に使用した SDK と同じバージョンが含まれるようにします。

実施例: Firestore を BigQuery にストリーミングする

変更前。拡張機能の functions/package.json は非公開で、拡張機能 ID を指定し、firebase-functions を直接の依存関係として宣言します。

{
  "name": "firestore-bigquery-export",
  "main": "lib/index.js",
  "private": true,
  "dependencies": {
    "@firebaseextensions/firestore-bigquery-change-tracker": "^2.0.4",
    "firebase-admin": "^14.2.0",
    "firebase-functions": "^6.3.2"
  }
}

変更後。公開可能なパッケージ: スコープ名、エクスポート マップ、firebase-functionspeerDependencies に移動:

{
  "name": "@firebase/firestore-bigquery-export",
  "version": "0.1.0",
  "main": "lib/index.js",
  "types": "lib/index.d.ts",
  "exports": {
    ".":     { "types": "./lib/index.d.ts", "default": "./lib/index.js" },  },
  "engines": { "node": ">=22" },
  "peerDependencies": { "firebase-functions": "^7.3.0" },
  "dependencies": {
      "@firebaseextensions/firestore-bigquery-change-tracker": "^2.0.4",
      "firebase-admin": "^14.2.0",
      "firebase-functions": "^7.3.0"
    }
}

関数を第 1 世代から第 2 世代にアップグレードする

拡張機能が第 1 世代の関数をエクスポートしている場合は、各関数を第 2 世代の同等の関数に変換します。firebase-functions/... モジュールからインポートし、関数オプションでランタイム設定を渡します。

第 2 世代のパッチ適用済みイベントの分割代入を使用すると、書き換え作業を最小限に抑えることができます。第 2 世代の SDK では V1 パラメータがイベント オブジェクトのフィールドとして公開されるため、分割代入されたパラメータや名前付きパラメータを使用して、ビジネス ロジックを変更せずに維持できます。

変更前。第 1 世代:

import * as functions from "firebase-functions/v1";

export const sync = functions.firestore
  .document("{collectionId}/{documentId}")
  .onWrite(async (change, context) => {
    await handleWrite(change.before, change.after, context.params);
  });

変更後。第 2 世代:

import { onDocumentWritten } from "firebase-functions/firestore";

export const syncV2 = onDocumentWritten(
  { document: "{collectionId}/{documentId}" },
  async ({change,context}) =>
    await handleWrite(change.before, change.after, context.params);
);

第 1 世代と第 2 世代の関数の違いの一覧については、Cloud Functions バージョンの比較をご覧ください。

拡張機能のパラメータとシークレットを変換する

パラメータを変換する

extension.yaml で宣言した各パラメータは Cloud Functions パラメータになります。

環境の直接読み取りを変換します。

const collectionPath = process.env.COLLECTION_PATH;

Cloud Functions パラメータに:

import { defineString } from "firebase-functions/params";
import { onDocumentWritten} from "firebase-functions/firestore";

const collectionPath = defineString("COLLECTION_PATH");

// Pass the param directly when used as a placeholder (e.g. trigger path)
export const sync = onDocumentWritten(
  { document: collectionPath },
  async (event) => {
    // Call .value() to read the string inside a handler
    const path = collectionPath.value();
    await handleWrite(path, event);
  }
);

ハンドラ内の文字列を読み取るには collectionPath.value() を使用します。プレースホルダが想定される場所(関数トリガー パスなど)では、collectionPath を直接使用します。

Firebase CLI はパラメータを検出し、.env、.env.projectId から値を読み取ります。または、デプロイ中にユーザーにプロンプトを表示します。既存のインストールの値が引き継がれるように、パラメータ名を同じにします。

コードで宣言したパラメータ名は絶対に変更しないでください。拡張機能の移行では、既存のエンドユーザー パラメータ値が自動的に保持されますが、これは名前が変更されていない場合に限られます。

動作例: Firestore を BigQuery にストリーミングする 前。extension.yaml で宣言されたパラメータ。config.ts で未加工の環境変数として読み取られます。

# extension.yaml
-   param: COLLECTION_PATH
  label: Collection path
  type: string
  required: true
// functions/src/config.ts
collectionPath: process.env.COLLECTION_PATH,

変更後。1 つの defineString。CLI がそれを検出し、.env から読み取ります。

// src/config.ts
import { defineString } from "firebase-functions/params";

collectionPath: defineString("COLLECTION_PATH", {
  label: "Collection path",
  // We now support "nonEmpty: true" to ensure a value other than the empty string
  // is entered, analagous to "required: true" in extensions.yaml
  input: { text: { nonEmpty: true} }
}),

パラメータ名は変更されていないため、既存の .env は引き続き機能します。

シークレットを変換する

extension.yaml で、type: secret を使用してシークレットを宣言します。拡張機能のランタイムはこれらを保存してバインドするため、拡張機能のコードは process.env.PARAM_NAME を直接読み取ることができます。一般的な Cloud Functions コードベースでは、各シークレットを明示的に宣言してバインドします。

import { defineSecret } from "firebase-functions/params";
import { onRequest } from "firebase-functions/https";

const apiKey = defineSecret("API_KEY");
export const fn = onRequest({ secrets: [apiKey] }, handler);

拡張機能が npm パッケージ/キットに移行されると、シークレット参照はエンドユーザーの .env ファイルで管理されます。コードで宣言されたシークレット名を一切変更しないことが重要です。移行中、エンドユーザーのシークレットは適宜移行されます。

実施例: Cloud Firestore からメールをトリガーする

変更前。config.ts で MAIL_COLLECTION と SMTP_PASSWORD が未加工の環境変数として読み取られます。

# extension.yaml
-   param: MAIL_COLLECTION
  label: Email documents collection
  type: string
  default: mail
  required: true

-   param: SMTP_PASSWORD
  label: SMTP password
  type: secret
// functions/src/config.ts
mailCollection: process.env.MAIL_COLLECTION,
smtpPassword: process.env.SMTP_PASSWORD,

変更後。1 つの defineString と 1 つの defineSecret。CLI は両方を検出し、.env から読み取ります。

import { defineString, defineSecret } from "firebase-functions/params";
import { onDocumentWritten } from "firebase-functions/firestore";

const mailCollection = defineString("MAIL_COLLECTION",{ label: "Email documents collection",
 default: "mail"
});

const smtpPassword = defineSecret("SMTP_PASSWORD", { label: "SMTP password" });

export const processQueue = onDocumentWritten(
  { document: `${mailCollection}/{documentId}`, secrets: [smtpPassword] },
  async (event) => {
    const collection = mailCollection.value();
    const password = smtpPassword.value();
    // ...
  }
);

内部タスクキュー呼び出しを移行する

一部の拡張機能は、Firebase Admin SDK を使用して、関数コード内から独自のタスクキューに作業をキューに追加します。これは、ディスパッチされたタスクを受信することとは異なります(関数のアップグレードライフサイクル フックの変換のセクションで説明しています)。ここで、コードは queue.enqueue(...) を呼び出すプロデューサーです。

以前のバージョンの Admin SDK では、同じ拡張機能内のタスクキュー関数をターゲットにするために、拡張機能が独自の拡張機能インスタンス ID を 2 番目のパラメータとして渡す必要がありました。`firebase-admin` 14.2.0 以降では、これは必須でも推奨でもありません。Task Queue API は、デフォルトで同じコンテキスト(拡張機能など)のタスクキューをターゲットにするようになりました。このパラメータは、拡張機能としてもスタンドアロン関数としても、コードから安全に削除できます。このパラメータを削除すると、ポータビリティと上位互換性が確保されます。

エンキュー呼び出しに関するその他のすべての情報(ロケーション/region/関数/name リソースパス、タスク ペイロード、再試行ロジックなど)は変更されません。

Cloud Tasks を使用した関数のキュー登録の詳細については、/docs/functions/task-functions をご覧ください。

変更前。第 1 世代の拡張機能

import { getFunctions } from "firebase-admin/functions";

const queue = getFunctions().taskQueue(
  `locations/${config.location}/functions/syncBigQuery`,
  process.env.EXT_INSTANCE_ID, // extension instance ID, injected by the runtime
);
await queue.enqueue(taskData);

変更後。第 2 世代の拡張機能

import { getFunctions } from "firebase-admin/functions";
import { region } from "firebase-functions/params";

const queue = getFunctions().taskQueue(
  `locations/${region.value()}/functions/syncBigQuery`);
await queue.enqueue(taskData);

エンキュー呼び出しが接頭辞付きのコードベースをターゲットにしている場合、検出された関数名にも接頭辞が付きます(例: orders-syncBigQuery)。

必要な API と IAM ロールを宣言する

拡張機能の IAM と API の要件を extension.yaml からコードに移動します。

import { requiresAPI, requiresRole } from "firebase-functions"

requiresAPI("bigquery.googleapis.com", "Needed to write changelog rows");
requiresRole("roles/bigquery.dataEditor");
requiresRole("roles/bigquery.user");

宣言型セキュリティを使用すると、Firebase CLI はコードベースのマネージド ランタイム サービス アカウントを作成または更新し、宣言されたすべてのロールの和集合を付与します。最終的な API がより狭いモデルをサポートしていない限り、コードベース内のすべての関数がこれらのロールで実行されることをユーザー向けに文書化します。

実施例: Firestore から BigQuery へのストリーミング

変更前。extension.yaml で宣言されています。Extensions ランタイムが API を有効にし、マネージド アカウントにロールを付与しました。

apis:
  -   apiName: bigquery.googleapis.com
roles:
  -   role: bigquery.dataEditor
  -   role: datastore.user
  -   role: bigquery.user

変更後。requiresAPI と requiresRole を使用してコードで宣言されます。

import { requiresAPI, requiresRole } from "firebase-functions/";
requiresAPI("bigquery.googleapis.com",
  "Needed to write changelog rows and views");
requiresRole("roles/biguqery.dataEditor");
requiresRole("roles/datastore.user");
requiresRole("roles/bigquery.user");

ライフサイクル フックを変換する

拡張機能が getExtensions().runtime()setProcessingStatesetFatalError など)を呼び出す場合は、それらの呼び出しを削除します。通常デプロイされた第 2 世代関数から呼び出されると、エラーがスローされるためです。ライフサイクル状態は afterFirstDeployafterRedeploy によって制御されるようになり、この状態のトラッキングは使用されなくなりました。

Firebase Extensions は、ユーザーが拡張機能をインストール、更新、再構成するときにセットアップを実行できます。npm パッケージで、同等のライフサイクル アクションをコードで宣言します。

1 回限りの設定の場合:

import { afterFirstDeploy } from "firebase-functions/lifecycle";
import { onTaskDispatched } from "firebase-functions/tasks";

export const runInitialSetup = onTaskDispatched(async (request) => {
  await initializeResources(request.data);
});

afterFirstDeploy({
  task: {
    function: "runInitialSetup",
    body: {}
  }
});

構成またはコードの更新の場合:

import { afterRedeploy } from "firebase-functions/lifecycle";

afterRedeploy({
  task: {
    function: "runInitialSetup",
    body: { reconcile: true }
  }
});

ライフサイクル アクションをべき等にします。ディスパッチまたは実行が失敗した場合、ユーザーは手動で再実行する必要がある場合があります。

firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME
firebase functions:lifecycle:run afterRedeploy CODEBASE_NAME

実施例: Firestore から BigQuery へのストリーミング

変更前。extension.yamllifecycleEvents(拡張機能ランタイムによる):

lifecycleEvents:
  onInstall:
    function: initBigQuerySync
    processingMessage: Configuring BigQuery Sync.
  onUpdate:
    function: setupBigQuerySync
    processingMessage: Configuring BigQuery Sync
  onConfigure:
    function: setupBigQuerySync
    processingMessage: Configuring BigQuery Sync

変更後。コードで宣言されます。タスクは最初のデプロイで BigQuery をプロビジョニングします。

import { afterFirstDeploy, afterRedeploy } from "firebase-functions/lifecycle";

afterFirstDeploy({ task: { function: "initBigQuerySync" } });
afterRedeploy({ task: { function: "setupBigQuerySync" } });

プロビジョニングはべき等であるため、再実行するとデータセット、テーブル、ビューが調整されます。ユーザーは、firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME を使用して手動で再実行できます。

ユーザー向けのドキュメント設定

  • 少なくとも次の内容を説明するパッケージ README を記述します。

  • パッケージに必要な .env 値。

  • パッケージに必要なシークレットと、既存のシークレット値を移行する方法。

  • パッケージが requiresRole(...) で宣言する IAM ロール。

  • パッケージが有効にするか、必要とする Google API。

  • パッケージが宣言するライフサイクル フックと、それらを手動で再実行する方法。

  • 請求メモ。

  • 元の拡張機能と比較して変更された点。

実施例: Firestore から BigQuery へのストリーミング

パッケージの README には、具体的な「変更内容」の表が記載されています。

懸念事項 拡張機能として @firebase/firestore-bigquery-export として
Config 拡張機能パラメータ .env 経由の関数パラメータ
IAM 拡張機能によって付与 requiresRole(...)(デプロイ時に適用)
プロビジョニング 拡張機能別のライフサイクル タスク afterFirstDeploy / afterRedeploy タスク
関数名 ext-instanceId-fsexportbigquery fsexportbigquery(接頭辞は省略可)

第 2 世代の関数をテストする

これで、デプロイ時に拡張機能の新規インストールと同じ動作をする第 2 世代の関数が作成されました。最後の手順は、途中で誤って導入された問題を検証して修正することです。

firebase-tools >= 15.24.0 を使用し、変換した第 2 世代関数を、動作をテストするための適切なリソースを含むテスト プロジェクトにデプロイします。拡張機能のテスト用にテスト プロジェクトがすでに設定されている場合は、次のコマンドを使用します。

firebase deploy --only functions

このコマンドを入力したら、表示されたウィザードにパラメータ値を入力します。これは、拡張機能の Firebase コンソールでインストール フォームに入力するのと同じ方法で行います。

実施例: Firestore を BigQuery にストリーミングする

Cloud Firestore から BigQuery への同期をエンドツーエンドで検証します。

  1. Cloud Firestore コンソールで、COLLECTION_PATH(users)として設定したコレクションがまだ存在しない場合は、作成します。
  2. 任意の値を設定した任意のフィールドを含む bigquery-mirror-test というドキュメントを作成します。
  3. BigQuery コンソールで、未加工の変更ログ テーブルに対してクエリを実行します。ドキュメントの作成を記録する 1 行を含む必要があります。
SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
  1. 最新のビューをクエリします。存在する唯一のドキュメント bigquery-mirror-test に関する最新の変更イベントが返されます。
SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
  1. Cloud Firestorebigquery-mirror-test ドキュメントを削除します。最新のビューから消え、DELETE イベントが未加工の変更ログ テーブルに追加されます。

単一のドキュメントの履歴全体を検査するには、次のコマンドを使用します。

SELECT *
   FROM `PROJECT_ID.analytics.users_raw_changelog`
   WHERE document_name = "bigquery-mirror-test"
   ORDER BY timestamp ASC

拡張機能のテストとの違い:

  • トリガーは ext-&lt;instanceId&gt;-fsexportbigquery ではなく fsexportbigquery(必要に応じて codebase プレフィックス付き)としてデプロイされます。Cloud Functions ダッシュボードとログでその名前を探します。
  • これで、コードは通常の関数として Firebase Local Emulator Suite で実行されます。エミュレータで使用するパラメータの値を .env.local で設定できます。Cloud Functions の単体テストで説明されているように、firebase-functions-test SDK を使用してコードの単体テストを行うこともできます。
  • プロビジョニングは Extensions ランタイムによって行われなくなりました。デプロイ後に変更ログ テーブルが見つからない場合は、設定タスク firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME を手動で再実行します。このタスクはべき等であるため、再実行するとデータセット、テーブル、ビューが調整されます。
  • パラメータ値はインストール フォームではなく .env から取得されるため、.env が完了すると firebase deploy の再実行はインタラクティブではなくなります。