はじめに: 最初の関数の記述、テスト、デプロイ


Cloud Functions を使用するにあたり、まずはチュートリアルを実践してください。このチュートリアルでは、必要な設定タスクから始め、次の 2 つの関連する関数を作成、テスト、デプロイします。

  • メッセージを追加する関数。テキスト値を受け取る URL を公開して Cloud Firestore に書き込みます。
  • テキストを大文字に変換する関数。Cloud Firestore の書き込みでトリガーされ、テキストを大文字に変換します。

このサンプルでは、Cloud Firestore と HTTP でトリガーされる JavaScript 関数を選択しました。その理由の 1 つとしては、これらのバックグラウンド トリガーは Firebase Local Emulator Suite で十分にテストできるためです。このツールセットでは、Realtime Database、Pub/Sub、Auth、HTTP 呼び出し可能トリガーもサポートされています。このページでは説明されていませんが、Remote Config、TestLab、アナリティクスのトリガーなど、他の種類のバックグラウンド トリガーもすべて対応するツールセットを使用してインタラクティブにテストできます。

このチュートリアルの以下のセクションでは、サンプルをビルド、テスト、デプロイするために必要な手順について詳しく説明します。コードの実行と検査のみを行う場合は、完成したサンプルコードの確認に進んでください。

Firebase プロジェクトを作成する

  1. Firebase コンソールで [プロジェクトを追加] をクリックします。

    • Firebase リソースを既存の Google Cloud プロジェクトに追加するには、そのプロジェクト名を入力するか、プルダウン メニューから選択します。

    • 新しいプロジェクトを作成するには、任意のプロジェクト名を入力します。必要に応じて、プロジェクト名の下に表示されるプロジェクト ID を編集することもできます。

  2. Firebase の利用規約が表示されたら、内容を読み、同意します。

  3. [続行] をクリックします。

  4. (省略可)プロジェクトに対し Google アナリティクスを設定します。これにより、次の Firebase プロダクトを使用する際のエクスペリエンスを最適化できます。

    既存の Google アナリティクス アカウントを選択するか、新しいアカウントを作成します。

    新しいアカウントを作成する場合は、アナリティクス レポートのロケーションを選択し、プロジェクトのデータ共有設定と Google アナリティクスの規約に同意します。

  5. [プロジェクトを作成](既存の Google Cloud プロジェクトを使用する場合は [Firebase を追加])をクリックします。

Firebase プロジェクトのリソースが自動的にプロビジョニングされます。処理が完了すると、Firebase コンソールに Firebase プロジェクトの概要ページが表示されます。

Node.js と Firebase CLI を設定する

関数を作成するには Node.js 環境が必要です。また、Cloud Functions ランタイムに関数をデプロイするには Firebase CLI が必要です。Node.js と npm をインストールする場合は、Node Version Manager をおすすめします。

Node.js と npm をインストールしたら、お好みの方法を使用して Firebase CLI をインストールします。npm を使用して CLI をインストールするには、次のコマンドを使用します。

npm install -g firebase-tools

これにより、グローバルに使用できる firebase コマンドがインストールされます。コマンドが失敗した場合は、npm アクセス権の変更が必要になる場合があります。firebase-tools を最新バージョンに更新するには、同じコマンドを再実行します。

プロジェクトの初期化

Firebase SDK for Cloud Functions を初期化するときに、依存関係と最小限のサンプルコードを含む空のプロジェクトを作成します。また、関数を作成するツールとして TypeScript か JavaScript のいずれかを選択します。このチュートリアルでは、Cloud Firestore も初期化する必要があります。

プロジェクトを初期化するには:

  1. firebase login を実行してブラウザからログインし、Firebase CLI を認証します。
  2. Firebase プロジェクトのディレクトリに移動します。
  3. firebase init firestore を実行します。このチュートリアルでは、Firestore ルールとインデックス ファイルのプロンプトが表示されたら、デフォルト値をそのまま使用してかまいません。このプロジェクトでまだ Cloud Firestore を使用していない場合は、Cloud Firestore を使ってみるの説明に沿って、Firestore の開始モードとロケーションも選択する必要があります。
  4. firebase init functions を実行します。既存のコードベースを選択するか、新しいコードベースを初期化して名前を付けるように求められます。最初は、デフォルトの場所にある単一のコードベースで十分です。後で実装を拡張する場合は、コードベースの関数を整理することをおすすめします。
  5. CLI は 2 つの言語をサポートしています。

    このチュートリアルでは、JavaScript を選択します。

  6. CLI には、npm で依存関係をインストールするオプションがあります。依存関係を別の方法で管理する場合は、このオプションを選択する必要はありません。ただし、選択しない場合でも、関数のエミュレートまたはデプロイの前に npm install を実行する必要があります。

これらのコマンドが正常に完了すると、プロジェクト構造は次のようになります。

myproject
 +- .firebaserc    # Hidden file that helps you quickly switch between
 |                 # projects with `firebase use`
 |
 +- firebase.json  # Describes properties for your project
 |
 +- functions/     # Directory containing all your functions code
      |
      +- .eslintrc.json  # Optional file containing rules for JavaScript linting.
      |
      +- package.json  # npm package file describing your Cloud Functions code
      |
      +- index.js      # main source file for your Cloud Functions code
      |
      +- node_modules/ # directory where your dependencies (declared in
                       # package.json) are installed

初期化時に作成された package.json ファイルには、重要なキー "engines": {"node": "16"} が格納されています。これにより、関数の作成とデプロイ用の Node.js バージョンを指定します。他のサポート対象バージョンを選択することもできます。

必要なモジュールをインポートしてアプリを初期化する

設定が完了したら、以降のセクションで説明するように、ソース ディレクトリを開いてコードを追加します。このサンプルでは、プロジェクトはノード require ステートメントを使用して Cloud Functions と Admin SDK モジュールをインポートする必要があります。index.js ファイルに次のような行を追加します。

// The Cloud Functions for Firebase SDK to create Cloud Functions and set up triggers.
const functions = require('firebase-functions/v1');

// The Firebase Admin SDK to access Firestore.
const admin = require("firebase-admin");
admin.initializeApp();

これらの行によって firebase-functions および firebase-admin モジュールが読み込まれ、Cloud Firestore の変更が可能な admin アプリ インスタンスが初期化されます。Admin SDK は FCM、Authentication、Firebase Realtime Database に対応しているため、これが利用可能な場所ではどこでも、Cloud Functions を使用して効果的に Firebase を統合できます。

Firebase CLI では、プロジェクトの初期化時に Firebase および Firebase SDK for Cloud Functions ノード モジュールが自動的にインストールされます。サードパーティ ライブラリをプロジェクトに追加するには、package.json を変更して npm install を実行します。詳細については、依存関係の扱いをご覧ください。

addMessage() 関数を追加する

addMessage() 関数については、index.js に次の行を追加します。

// Take the text parameter passed to this HTTP endpoint and insert it into
// Firestore under the path /messages/:documentId/original
exports.addMessage = functions.https.onRequest(async (req, res) => {
  // Grab the text parameter.
  const original = req.query.text;
  // Push the new message into Firestore using the Firebase Admin SDK.
  const writeResult = await admin
    .firestore()
    .collection("messages")
    .add({ original: original });
  // Send back a message that we've successfully written the message
  res.json({ result: `Message with ID: ${writeResult.id} added.` });
});

addMessage() 関数は、HTTP エンドポイントです。エンドポイントに対するリクエストを行うと、Express.JS スタイルの Request オブジェクトと Response オブジェクトが onRequest() コールバックに渡されます。

呼び出し可能な関数と同様に HTTP 関数は同期的です。このため、できるだけ早くレスポンスを送信し、Cloud Firestore による作業を遅らせる必要があります。addMessage() HTTP 関数は、テキスト値を HTTP エンドポイントに渡し、/messages/:documentId/original パスの下でデータベースに挿入します。

makeUppercase() 関数を追加する

makeUppercase() 関数については、index.js に次の行を追加します。

// Listens for new messages added to /messages/:documentId/original and creates an
// uppercase version of the message to /messages/:documentId/uppercase
exports.makeUppercase = functions.firestore
  .document("/messages/{documentId}")
  .onCreate((snap, context) => {
    // Grab the current value of what was written to Firestore.
    const original = snap.data().original;

    // Access the parameter `{documentId}` with `context.params`
    functions.logger.log("Uppercasing", context.params.documentId, original);

    const uppercase = original.toUpperCase();

    // You must return a Promise when performing asynchronous tasks inside a Functions such as
    // writing to Firestore.
    // Setting an 'uppercase' field in Firestore document returns a Promise.
    return snap.ref.set({ uppercase }, { merge: true });
  });

makeUppercase() 関数は、Cloud Firestore に書き込まれるときに実行されます。ref.set 関数ではリッスン対象のドキュメントを定義します。パフォーマンス上の理由から、可能な限り具体的にする必要があります。

中かっこ({documentId} など)は、「パラメータ」を囲みます。これは、コールバックで一致したデータを公開するワイルドカードです。

新しいメッセージが追加されるたびに、Cloud Firestore は onCreate() コールバックをトリガーします。

Cloud Firestore イベントなどのイベント ドリブンの関数は非同期です。コールバック関数は、null、オブジェクト、Promise のいずれかを返す必要があります。何も返さない場合、関数はタイムアウトして、エラーを通知し、再試行されます。同期、非同期、Promise をご覧ください。

関数の実行をエミュレートする

Firebase Local Emulator Suite を使用すると、Firebase プロジェクトにデプロイする代わりにローカルマシンでアプリをビルドしてテストできます。開発中にローカルテストを行うことを強くおすすめします。その理由の 1 つとしては、本番環境でコストを発生させる可能性があるコーディング エラーのリスク(無限ループなど)が低下するためです。

関数をエミュレートするには:

  1. firebase emulators:start を実行し、Emulator Suite UI の URL の出力を確認します。デフォルトは localhost:4000 ですが、お使いのマシンによっては別のポートでホストされている場合があります。その URL をブラウザに入力して、Emulator Suite UI を開きます。

  2. firebase emulators:start コマンドの出力で、HTTP 関数 addMessage() の URL を確認します。この URL は次のような形式です。http://localhost:5001/MY_PROJECT/us-central1/addMessage。ただし:

    1. MY_PROJECT はプロジェクト ID に置き換えられます。
    2. お使いのローカルマシンによってはポートが異なる場合があります。
  3. 関数の URL の末尾にクエリ文字列 ?text=uppercaseme を追加します。http://localhost:5001/MY_PROJECT/us-central1/addMessage?text=uppercaseme のようになります。メッセージ「uppercaseme」をカスタム メッセージに変更することもできます。

  4. ブラウザの新しいタブで URL を開いて、新しいメッセージを作成します。

  5. Emulator Suite UI で関数の動作を確認します。

    1. [ログ] タブに、addMessage() 関数と makeUppercase() 関数が実行されたことを示す新しいログが表示されます。

      i functions: Beginning execution of "addMessage"

      i functions: Beginning execution of "makeUppercase"

    2. [Firestore] タブに、元のメッセージと大文字に変換されたメッセージを含むドキュメントが表示されます(元のメッセージが「uppercaseme」だった場合、変換後は「UPPERCASEME」となります)。

本番環境に関数をデプロイする

エミュレータで関数が意図したとおりに動作するようになったら、本番環境への関数のデプロイ、テスト、実行に進むことができます。推奨される Node.js 14 ランタイム環境にデプロイするには、プロジェクトが Blaze 料金プランを利用している必要がある点にご注意ください。Cloud Functions の料金をご覧ください。

チュートリアルを完了するには、関数をデプロイしてから addMessage() を実行して makeUppercase() をトリガーします。

  1. 次のコマンドを実行して、関数をデプロイします。

     firebase deploy --only functions
     

    このコマンドを実行すると、Firebase CLI は HTTP 関数のエンドポイントの URL を出力します。デバイスには、次のような行が表示されます。

    Function URL (addMessage): https://us-central1-MY_PROJECT.cloudfunctions.net/addMessage
    

    この URL には、プロジェクト ID と HTTP 関数のリージョンが含まれています。ここでは必要ありませんが、本番環境の HTTP 関数では、ネットワーク レイテンシを最小限に抑えるため、ロケーションの指定が必要になる場合があります。

    「プロジェクトへのアクセスを許可できません」などのアクセスエラーが発生した場合は、プロジェクト エイリアスを確認してみてください。

  2. CLI によって出力された addMessage() URL を使用して、テキスト クエリ パラメータを追加し、ブラウザで開きます。

    https://us-central1-MY_PROJECT.cloudfunctions.net/addMessage?text=uppercasemetoo
    

    関数によりブラウザが実行され、テキスト文字列が格納されているデータベースの場所にある Firebase コンソールにリダイレクトされます。この書き込みイベントによって、大文字の文字列を書き込む makeUppercase() がトリガーされます。

関数をデプロイして実行した後、Google Cloud コンソールでログを表示できます。開発環境または本番環境で関数を削除する必要がある場合は、Firebase CLI を使用します。

本番環境では、実行するインスタンスの最小数と最大数を設定して関数のパフォーマンスを最適化し、コストを抑えられます。これらのランタイム オプションの詳細については、スケーリング動作を制御するをご覧ください。

完成したサンプルコードの確認

ここでは、関数 addMessage()makeUppercase() を含む完成した functions/index.js を示します。これらの関数を使用すると、Cloud Firestore に値を書き込み、文字列内のすべての文字を大文字にして変換する HTTP エンドポイントにパラメータを渡すことができます。

// The Cloud Functions for Firebase SDK to create Cloud Functions and set up triggers.
const functions = require('firebase-functions/v1');

// The Firebase Admin SDK to access Firestore.
const admin = require("firebase-admin");
admin.initializeApp();

// Take the text parameter passed to this HTTP endpoint and insert it into
// Firestore under the path /messages/:documentId/original
exports.addMessage = functions.https.onRequest(async (req, res) => {
  // Grab the text parameter.
  const original = req.query.text;
  // Push the new message into Firestore using the Firebase Admin SDK.
  const writeResult = await admin
    .firestore()
    .collection("messages")
    .add({ original: original });
  // Send back a message that we've successfully written the message
  res.json({ result: `Message with ID: ${writeResult.id} added.` });
});

// Listens for new messages added to /messages/:documentId/original and creates an
// uppercase version of the message to /messages/:documentId/uppercase
exports.makeUppercase = functions.firestore
  .document("/messages/{documentId}")
  .onCreate((snap, context) => {
    // Grab the current value of what was written to Firestore.
    const original = snap.data().original;

    // Access the parameter `{documentId}` with `context.params`
    functions.logger.log("Uppercasing", context.params.documentId, original);

    const uppercase = original.toUpperCase();

    // You must return a Promise when performing asynchronous tasks inside a Functions such as
    // writing to Firestore.
    // Setting an 'uppercase' field in Firestore document returns a Promise.
    return snap.ref.set({ uppercase }, { merge: true });
  });

次のステップ

このドキュメントでは、Cloud Functions の関数の管理方法と、Cloud Functions でサポートされているすべてのイベントタイプを処理する方法について説明しました。

Cloud Functions についてさらに学習するために、次のことも実践できます。