Cloud Functions による Cloud Firestore の拡張

Cloud Functions を使用して Node.js コードをデプロイすることで、Cloud Firestore データベースの変更によってトリガーされるイベントを処理できます。これにより、独自のサーバーを運用することなく、サーバーサイドの機能を簡単にアプリに追加できます。

ユースケースの例については、Cloud Functions でできること、または関数サンプル GitHub リポジトリをご覧ください。

Cloud Firestore 関数トリガー

Cloud Functions for Firebase SDK がエクスポートする functions.firestore オブジェクトを使用して、特定の Cloud Firestore イベントに結び付けたハンドラを作成できます。

イベントタイプ トリガー
onCreate ドキュメントが最初に書き込まれたときにトリガーされます。
onUpdate すでに存在するドキュメントの値が変更されたときにトリガーされます。
onDelete データを含むドキュメントが削除されたときにトリガーされます。
onWrite onCreateonUpdate または onDelete がトリガーされたときにトリガーされます。

Cloud Functions for Firebase に対してプロジェクトを有効にしていない場合は、はじめに: 最初の関数の記述とデプロイを参照して、Cloud Functions for Firebase プロジェクトを構成して設定します。

Cloud Firestore でトリガーされる関数の作成

関数のトリガーを定義する

Cloud Firestore トリガーを定義するには、ドキュメント パスとイベントタイプを指定します。

Node.js

const functions = require('firebase-functions');

exports.myFunction = functions.firestore
  .document('my-collection/{docId}')
  .onWrite((change, context) => { /* ... */ });

ドキュメント パスは、特定のドキュメントまたはワイルドカード パターンのいずれかを参照できます。

単一のドキュメントを指定する

特定のドキュメントが変更されたときにイベントをトリガーするには、次の関数を使用します。

Node.js

// Listen for any change on document `marie` in collection `users`
exports.myFunctionName = functions.firestore
    .document('users/marie').onWrite((change, context) => {
      // ... Your code here
    });

ワイルドカードを使用してドキュメントのグループを指定する

特定のコレクション内の任意のドキュメントなど、ドキュメントのグループにトリガーを関連付ける場合は、ドキュメント ID の代わりに {wildcard} を使用します。

Node.js

// Listen for changes in all documents in the 'users' collection
exports.useWildcard = functions.firestore
    .document('users/{userId}')
    .onWrite((change, context) => {
      // If we set `/users/marie` to {name: "Marie"} then
      // context.params.userId == "marie"
      // ... and ...
      // change.after.data() == {name: "Marie"}
    });

この例では、users にあるドキュメントの任意のフィールドが変更されると、userId というワイルドカードと照合されます。

users に含まれるドキュメントにサブコレクションがある場合、サブコレクションのいずれかに含まれるドキュメントのフィールドが変更されても、userId ワイルドカードはトリガーされません。

ワイルドカードに一致した部分がドキュメント パスから抽出され、context.params に保存されます。明示的なコレクションまたはドキュメント ID に置き換えるワイルドカードは、必要な数だけ定義できます。次に例を示します。

Node.js

// Listen for changes in all documents in the 'users' collection and all subcollections
exports.useMultipleWildcards = functions.firestore
    .document('users/{userId}/{messageCollectionId}/{messageId}')
    .onWrite((change, context) => {
      // If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
      // context.params.userId == "marie";
      // context.params.messageCollectionId == "incoming_messages";
      // context.params.messageId == "134";
      // ... and ...
      // change.after.data() == {body: "Hello"}
    });

イベント トリガー

新しいドキュメントが作成されたときに関数をトリガーする

onCreate() ハンドラでワイルドカードを使用すると、コレクションで新しいドキュメントが作成されるたびに関数をトリガーできます。この例の関数は、新しいユーザー プロフィールが追加されるたびに createUser を呼び出します。

Node.js

exports.createUser = functions.firestore
    .document('users/{userId}')
    .onCreate((snap, context) => {
      // Get an object representing the document
      // e.g. {'name': 'Marie', 'age': 66}
      const newValue = snap.data();

      // access a particular field as you would any JS property
      const name = newValue.name;

      // perform desired operations ...
    });

ドキュメントが更新されたときに関数をトリガーする

onUpdate() 関数でワイルドカードを使用して、ドキュメントが更新されたときに関数をトリガーすることもできます。この例の関数は、ユーザーがプロフィールを変更すると、updateUser を呼び出します。

Node.js

exports.updateUser = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
      // Get an object representing the document
      // e.g. {'name': 'Marie', 'age': 66}
      const newValue = change.after.data();

      // ...or the previous value before this update
      const previousValue = change.before.data();

      // access a particular field as you would any JS property
      const name = newValue.name;

      // perform desired operations ...
    });

ドキュメントが削除されたときに関数をトリガーする

onDelete() 関数でワイルドカードを使用して、ドキュメントが削除されたときに関数をトリガーすることもできます。次の例の関数は、ユーザーがユーザー プロフィールを削除すると deleteUser を呼び出します。

Node.js

exports.deleteUser = functions.firestore
    .document('users/{userID}')
    .onDelete((snap, context) => {
      // Get an object representing the document prior to deletion
      // e.g. {'name': 'Marie', 'age': 66}
      const deletedValue = snap.data();

      // perform desired operations ...
    });

ドキュメントに対するすべての変更に対して関数をトリガーする

発生するイベントの種類に対して必要な要件が存在しない場合は、ワイルドカードを配置したonWrite() 関数を使用して Cloud Firestore ドキュメントのすべての変更をリッスンできます。次のサンプル関数では、ユーザーが作成、更新、または削除された場合に modifyUser が呼び出されます。

Node.js

exports.modifyUser = functions.firestore
    .document('users/{userID}')
    .onWrite((change, context) => {
      // Get an object with the current document value.
      // If the document does not exist, it has been deleted.
      const document = change.after.exists ? change.after.data() : null;

      // Get an object with the previous document value (for update or delete)
      const oldDocument = change.before.data();

      // perform desired operations ...
    });

データの読み取りと書き込み

関数がトリガーされると、イベントに関連するデータのスナップショットが提供されます。このスナップショットを使用してイベントをトリガーしたドキュメントの読み書きをしたり、Firebase Admin SDK を使用してデータベースの他の部分にアクセスしたりできます。

イベントデータ

データの読み取り

関数がトリガーされた後、更新済みのドキュメントのデータを取得したり、更新前のデータを取得したりする必要が生じる場合があります。更新前のデータは、更新前のドキュメントのスナップショットを含む change.before.data() を使用して取得できます。同様に、change.after.data() には更新後のドキュメントのスナップショットの状態が含まれます。

Node.js

exports.updateUser2 = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
      // Get an object representing the current document
      const newValue = change.after.data();

      // ...or the previous value before this update
      const previousValue = change.before.data();
    });

プロパティには、他のオブジェクトの場合と同様にアクセスできます。または、get 関数を使用して特定のフィールドにアクセスすることもできます。

Node.js

// Fetch data using standard accessors
const age = snap.data().age;
const name = snap.data()['name'];

// Fetch data using built in accessor
const experience = snap.get('experience');

データの書き込み

それぞれの関数呼び出しは、Cloud Firestore データベースの特定のドキュメントに関連付けられています。このドキュメントには、関数に返されたスナップショットの ref プロパティの DocumentReference でアクセスできます。

この DocumentReferenceCloud Firestore Node.js SDK で提供され、update()set()remove() などのメソッドが含まれているため、関数をトリガーしたドキュメントを簡単に変更できます。

Node.js

// Listen for updates to any `user` document.
exports.countNameChanges = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
      // Retrieve the current and previous value
      const data = change.after.data();
      const previousData = change.before.data();

      // We'll only update if the name has changed.
      // This is crucial to prevent infinite loops.
      if (data.name == previousData.name) {
        return null;
      }

      // Retrieve the current count of name changes
      let count = data.name_change_count;
      if (!count) {
        count = 0;
      }

      // Then return a promise of a set operation to update the count
      return change.after.ref.set({
        name_change_count: count + 1
      }, {merge: true});
    });

トリガー イベント外のデータ

Cloud Functions は信頼できる環境で実行されます。つまり、プロジェクトのサービス アカウントとして承認されているということです。Firebase Admin SDK を使用して、読み取りと書き込みを実行できます。

Node.js

const admin = require('firebase-admin');
admin.initializeApp();

const db = admin.firestore();

exports.writeToFirestore = functions.firestore
  .document('some/doc')
  .onWrite((change, context) => {
    db.doc('some/otherdoc').set({ ... });
  });

制限事項

Cloud FunctionsCloud Firestore トリガーには、次の制限事項があることに留意してください。

  • Cloud Functions(第 1 世代)では、Firestore ネイティブ モードで既存の「(デフォルト)」データベースがあることが前提となります。Cloud Firestore の名前付きデータベースや Datastore モードはサポートされていません。このような場合にイベントを構成するには、Cloud Functions(第 2 世代)を使用してください。
  • 順序は保証されません。短時間に複数の変更を行うと、予期しない順序で関数の呼び出しがトリガーされることがあります。
  • イベントは必ず 1 回以上処理されますが、1 つのイベントで関数が複数回呼び出される場合があります。「正確に 1 回」のメカニズムに依存することは避け、べき等性がある関数を記述してください。
  • Datastore モードの Cloud Firestore には、Cloud Functions(第 2 世代)が必要です。Cloud Functions(第 1 世代)では、Datastore モードはサポートされていません。
  • トリガーは、単一のデータベースに関連付けられます。複数のデータベースに一致するトリガーは作成できません。
  • データベースを削除しても、そのデータベースのトリガーは自動的に削除されません。トリガーはイベントの配信を停止しますが、トリガーを削除するまで存在し続けます。
  • 一致したイベントが最大リクエスト サイズを超えると、イベントが Cloud Functions(第 1 世代)に配信されない場合があります。
    • リクエスト サイズが原因で配信されなかったイベントは、プラットフォーム ログに記録され、プロジェクトのログ使用量にカウントされます。
    • これらのログは、ログ エクスプローラで「サイズが第 1 世代の上限を超えているため、イベントを Cloud Functions に配信できません...」という error 重大度メッセージとともに表示されます。関数名は functionName フィールドで確認できます。receiveTimestamp フィールドが現在から 1 時間以内であれば、タイムスタンプの前後のスナップショットで問題のドキュメントを読み取ることで、実際のイベントの内容を推測できます。
    • このようなケイデンスを回避するには、次のようにします。
      • Cloud Functions(第 2 世代)に移行してアップグレードする
      • ドキュメントのサイズを縮小する
      • 問題の Cloud Functions を削除する
    • 除外を使用してロギング自体を無効にすることもできますが、問題のあるイベントは引き続き配信されないことに注意してください。