このガイドでは、非推奨の 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.md、PREINSTALL.md、POSTINSTALL.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-functions が peerDependencies に移動:
{
"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()(setProcessingState や setFatalError など)を呼び出す場合は、それらの呼び出しを削除します。通常デプロイされた第 2 世代関数から呼び出されると、エラーがスローされるためです。ライフサイクル状態は afterFirstDeploy と afterRedeploy によって制御されるようになり、この状態のトラッキングは使用されなくなりました。
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.yaml の lifecycleEvents(拡張機能ランタイムによる):
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 への同期をエンドツーエンドで検証します。
- Cloud Firestore コンソールで、COLLECTION_PATH(users)として設定したコレクションがまだ存在しない場合は、作成します。
- 任意の値を設定した任意のフィールドを含む bigquery-mirror-test というドキュメントを作成します。
- BigQuery コンソールで、未加工の変更ログ テーブルに対してクエリを実行します。ドキュメントの作成を記録する 1 行を含む必要があります。
SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
- 最新のビューをクエリします。存在する唯一のドキュメント
bigquery-mirror-testに関する最新の変更イベントが返されます。
SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
- Cloud Firestore の
bigquery-mirror-testドキュメントを削除します。最新のビューから消え、DELETEイベントが未加工の変更ログ テーブルに追加されます。
単一のドキュメントの履歴全体を検査するには、次のコマンドを使用します。
SELECT *
FROM `PROJECT_ID.analytics.users_raw_changelog`
WHERE document_name = "bigquery-mirror-test"
ORDER BY timestamp ASC
拡張機能のテストとの違い:
- トリガーは
ext-<instanceId>-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の再実行はインタラクティブではなくなります。