Cloud Functions で、コードをデプロイして、Cloud Firestore データベースの変更によってトリガーされるイベントを処理できます。これにより、独自のサーバーを運用することなく、サーバーサイドの機能を簡単にアプリに追加できます。
Cloud Functions(第 2 世代)
Cloud Run と Eventarc を活用した Cloud Functions for Firebase(第 2 世代)を利用すれば、インフラストラクチャのさらなる強化、パフォーマンスとスケーラビリティの高度な管理、関数のランタイムのより詳細な管理を実現できます。第 2 世代の詳細については、Cloud Functions for Firebase(第 2 世代)をご覧ください。そうではなく、第 1 世代の詳細を確認するには、Cloud Functions による Cloud Firestore の拡張をご覧ください。
Cloud Firestore 関数トリガー
Cloud Functions for Firebase SDK で次の Cloud Firestore イベント トリガーをエクスポートすると、特定の Cloud Firestore イベントに結び付けたハンドラを作成できます。
イベントタイプ | トリガー |
---|---|
onDocumentCreated |
ドキュメントが最初に書き込まれたときにトリガーされます。 |
onDocumentUpdated |
すでに存在するドキュメントの値が変更されたときにトリガーされます。 |
onDocumentDeleted |
ドキュメントが削除されたときにトリガーされます。 |
onDocumentWritten |
onDocumentCreated 、onDocumentUpdated または onDocumentDeleted がトリガーされたときにトリガーされます。 |
onDocumentCreatedWithAuthContext |
onDocumentCreated (追加の認証情報を含む) |
onDocumentWrittenWithAuthContext |
onDocumentWritten (追加の認証情報を含む) |
onDocumentDeletedWithAuthContext |
onDocumentDeleted (追加の認証情報を含む) |
onDocumentUpdatedWithAuthContext |
onDocumentUpdated (追加の認証情報を含む) |
イベントタイプ | トリガー |
---|---|
on_document_created |
ドキュメントが最初に書き込まれたときにトリガーされます。 |
on_document_updated |
すでに存在するドキュメントの値が変更されたときにトリガーされます。 |
on_document_deleted |
ドキュメントが削除されたときにトリガーされます。 |
on_document_written |
on_document_created 、on_document_updated または on_document_deleted がトリガーされたときにトリガーされます。 |
on_document_created_with_auth_context |
on_document_created (追加の認証情報を含む) |
on_document_updated_with_auth_context |
on_document_updated (追加の認証情報を含む) |
on_document_deleted_with_auth_context |
on_document_deleted (追加の認証情報を含む) |
on_document_written_with_auth_context |
on_document_written (追加の認証情報を含む) |
Cloud Firestore のイベントは、ドキュメントが変更された場合にのみトリガーされます。Cloud Firestore ドキュメントの更新でデータが変更されない場合は(オペレーションなしの書き込み)、更新イベントや書き込みイベントは生成されません。特定のフィールドにイベントを追加することはできません。
Cloud Functions for Firebase に対して有効なプロジェクトがまだない場合は、Cloud Functions for Firebase(第 2 世代)を使ってみるを参照して、Cloud Functions for Firebase プロジェクトを構成および設定してください。
Cloud Firestore でトリガーされる関数の作成
関数のトリガーを定義する
Cloud Firestore トリガーを定義するには、ドキュメント パスとイベントタイプを指定します。
import {
onDocumentWritten,
onDocumentCreated,
onDocumentUpdated,
onDocumentDeleted,
Change,
FirestoreEvent
} from "firebase-functions/v2/firestore";
exports.myfunction = onDocumentWritten("my-collection/{docId}", (event) => {
/* ... */
});
from firebase_functions.firestore_fn import (
on_document_created,
on_document_deleted,
on_document_updated,
on_document_written,
Event,
Change,
DocumentSnapshot,
)
@on_document_created(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot]) -> None:
ドキュメント パスは、特定のドキュメントまたはワイルドカード パターンのいずれかを参照できます。
単一ドキュメントを指定する
特定のドキュメントが変更されたときにイベントをトリガーするには、次の関数を使用します。
import {
onDocumentWritten,
Change,
FirestoreEvent
} from "firebase-functions/v2/firestore";
exports.myfunction = onDocumentWritten("users/marie", (event) => {
// Your code here
});
from firebase_functions.firestore_fn import (
on_document_written,
Event,
Change,
DocumentSnapshot,
)
@on_document_written(document="users/marie")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
ワイルドカードを使用してドキュメントのグループを指定する
特定のコレクション内の任意のドキュメントなど、ドキュメントのグループにトリガーを関連付ける場合は、ドキュメント ID の代わりに {wildcard}
を使用します。
import {
onDocumentWritten,
Change,
FirestoreEvent
} from "firebase-functions/v2/firestore";
exports.myfunction = onDocumentWritten("users/{userId}", (event) => {
// If we set `/users/marie` to {name: "Marie"} then
// event.params.userId == "marie"
// ... and ...
// event.data.after.data() == {name: "Marie"}
});
from firebase_functions.firestore_fn import (
on_document_written,
Event,
Change,
DocumentSnapshot,
)
@on_document_written(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
# If we set `/users/marie` to {name: "Marie"} then
event.params["userId"] == "marie" # True
# ... and ...
event.data.after.to_dict() == {"name": "Marie"} # True
この例では、users
にあるドキュメントの任意のフィールドが変更されると、userId
というワイルドカードと照合されます。
users
に含まれるドキュメントにサブコレクションがある場合、サブコレクションに含まれるいずれかのドキュメントのフィールドが変更されても、userId
ワイルドカードはトリガーされません。
ワイルドカードに一致した部分がドキュメント パスから抽出され、event.params
に保存されます。明示的なコレクションまたはドキュメント ID に置き換えるワイルドカードは、必要な数だけ定義できます。次に例を示します。
import {
onDocumentWritten,
Change,
FirestoreEvent
} from "firebase-functions/v2/firestore";
exports.myfunction = onDocumentWritten("users/{userId}/{messageCollectionId}/{messageId}", (event) => {
// If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
// event.params.userId == "marie";
// event.params.messageCollectionId == "incoming_messages";
// event.params.messageId == "134";
// ... and ...
// event.data.after.data() == {body: "Hello"}
});
from firebase_functions.firestore_fn import (
on_document_written,
Event,
Change,
DocumentSnapshot,
)
@on_document_written(document="users/{userId}/{messageCollectionId}/{messageId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
# If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
event.params["userId"] == "marie" # True
event.params["messageCollectionId"] == "incoming_messages" # True
event.params["messageId"] == "134" # True
# ... and ...
event.data.after.to_dict() == {"body": "Hello"}
ワイルドカードを使用する場合でも、トリガーは常にドキュメントを指している必要があります。たとえば、{messageCollectionId}
はコレクションであるため、users/{userId}/{messageCollectionId}
は無効になります。その一方で、{messageId}
は常にドキュメントを指すため、users/{userId}/{messageCollectionId}/{messageId}
は有効です。
イベント トリガー
ドキュメントが新規作成されたときに関数をトリガーする
コレクションで新しいドキュメントが作成されるたびに、関数をトリガーできます。この例の関数は、新しいユーザー プロフィールが追加されるたびにトリガーされます。
import {
onDocumentCreated,
Change,
FirestoreEvent
} from "firebase-functions/v2/firestore";
exports.createuser = onDocumentCreated("users/{userId}", (event) => {
// Get an object representing the document
// e.g. {'name': 'Marie', 'age': 66}
const snapshot = event.data;
if (!snapshot) {
console.log("No data associated with the event");
return;
}
const data = snapshot.data();
// access a particular field as you would any JS property
const name = data.name;
// perform more operations ...
});
追加の認証情報については、onDocumentCreatedWithAuthContext
を使用します。
from firebase_functions.firestore_fn import (
on_document_created,
Event,
DocumentSnapshot,
)
@on_document_created(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot]) -> None:
# Get a dictionary representing the document
# e.g. {'name': 'Marie', 'age': 66}
new_value = event.data.to_dict()
# Access a particular field as you would any dictionary
name = new_value["name"]
# Perform more operations ...
ドキュメントの更新時に関数をトリガーする
また、ドキュメントが更新されたときに関数をトリガーすることもできます。この例の関数は、ユーザーがプロフィールを変更すると実行されます。
import {
onDocumentUpdated,
Change,
FirestoreEvent
} from "firebase-functions/v2/firestore";
exports.updateuser = onDocumentUpdated("users/{userId}", (event) => {
// Get an object representing the document
// e.g. {'name': 'Marie', 'age': 66}
const newValue = event.data.after.data();
// access a particular field as you would any JS property
const name = newValue.name;
// perform more operations ...
});
追加の認証情報については、onDocumentUpdatedWithAuthContext
を使用します。
from firebase_functions.firestore_fn import (
on_document_updated,
Event,
Change,
DocumentSnapshot,
)
@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
# Get a dictionary representing the document
# e.g. {'name': 'Marie', 'age': 66}
new_value = event.data.after.to_dict()
# Access a particular field as you would any dictionary
name = new_value["name"]
# Perform more operations ...
ドキュメントの削除時に関数をトリガーする
ドキュメントが削除されたときに関数をトリガーすることもできます。この例の関数は、ユーザーがユーザー プロフィールを削除すると実行されます。
import {
onDocumentDeleted,
Change,
FirestoreEvent
} from "firebase-functions/v2/firestore";
exports.deleteuser = onDocumentDeleted("users/{userId}", (event) => {
// Get an object representing the document
// e.g. {'name': 'Marie', 'age': 66}
const snap = event.data;
const data = snap.data();
// perform more operations ...
});
追加の認証情報については、onDocumentDeletedWithAuthContext
を使用します。
from firebase_functions.firestore_fn import (
on_document_deleted,
Event,
DocumentSnapshot,
)
@on_document_deleted(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot|None]) -> None:
# Perform more operations ...
ドキュメントに対するすべての変更に対して関数をトリガーする
「ドキュメント書き込み」イベント トリガーを使用することによって、発生するイベントの種類に関係なく、Cloud Firestore ドキュメントのすべての変更をリッスンできます。この例の関数は、ユーザーが作成、更新、削除されたときに実行されます。
import {
onDocumentWritten,
Change,
FirestoreEvent
} from "firebase-functions/v2/firestore";
exports.modifyuser = onDocumentWritten("users/{userId}", (event) => {
// Get an object with the current document values.
// If the document does not exist, it was deleted
const document = event.data.after.data();
// Get an object with the previous document values
const previousValues = event.data.before.data();
// perform more operations ...
});
追加の認証情報については、onDocumentWrittenWithAuthContext
を使用します。
from firebase_functions.firestore_fn import (
on_document_written,
Event,
Change,
DocumentSnapshot,
)
@on_document_written(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot | None]]) -> None:
# Get an object with the current document values.
# If the document does not exist, it was deleted.
document = (event.data.after.to_dict()
if event.data.after is not None else None)
# Get an object with the previous document values.
# If the document does not exist, it was newly created.
previous_values = (event.data.before.to_dict()
if event.data.before is not None else None)
# Perform more operations ...
データの読み取りと書き込み
関数がトリガーされると、イベントに関連するデータのスナップショットが提供されます。このスナップショットを使用してイベントをトリガーしたドキュメントの読み書きをしたり、Firebase Admin SDK を使用してデータベースの他の部分にアクセスしたりできます。
イベントデータ
データの読み取り
関数がトリガーされた後、更新済みのドキュメントのデータを取得したり、更新前のデータを取得したりする必要が生じる場合があります。更新前のデータは、更新前のドキュメントのスナップショットを含む event.data.before
を使用して取得できます。同様に、event.data.after
には更新後のドキュメントのスナップショットの状態が含まれます。
exports.updateuser2 = onDocumentUpdated("users/{userId}", (event) => {
// Get an object with the current document values.
// If the document does not exist, it was deleted
const newValues = event.data.after.data();
// Get an object with the previous document values
const previousValues = event.data.before.data();
});
@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
# Get an object with the current document values.
new_value = event.data.after.to_dict()
# Get an object with the previous document values.
prev_value = event.data.before.to_dict()
プロパティには、他のオブジェクトの場合と同様にアクセスできます。または、get
関数を使用して特定のフィールドにアクセスすることもできます。
// Fetch data using standard accessors
const age = event.data.after.data().age;
const name = event.data.after.data()['name'];
// Fetch data using built in accessor
const experience = event.data.after.data.get('experience');
# Get the value of a single document field.
age = event.data.after.get("age")
# Convert the document to a dictionary.
age = event.data.after.to_dict()["age"]
データの書き込み
それぞれの関数呼び出しは、Cloud Firestore データベースの特定のドキュメントに関連付けられています。関数に返されたスナップショット内のドキュメントにアクセスできます。
ドキュメントの参照には update()
、set()
、remove()
などのメソッドが含まれているため、関数をトリガーしたドキュメントを変更できます。
import { onDocumentUpdated } from "firebase-functions/v2/firestore";
exports.countnamechanges = onDocumentUpdated('users/{userId}', (event) => {
// Retrieve the current and previous value
const data = event.data.after.data();
const previousData = event.data.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 data.after.ref.set({
name_change_count: count + 1
}, {merge: true});
});
@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
# Get the current and previous document values.
new_value = event.data.after
prev_value = event.data.before
# We'll only update if the name has changed.
# This is crucial to prevent infinite loops.
if new_value.get("name") == prev_value.get("name"):
return
# Retrieve the current count of name changes
count = new_value.to_dict().get("name_change_count", 0)
# Update the count
new_value.reference.update({"name_change_count": count + 1})
ユーザー認証情報にアクセスする
次のいずれかのイベントタイプを使用する場合は、イベントをトリガーしたプリンシパルに関するユーザー認証情報にアクセスできます。 この情報は、ベースイベントで返される情報に追加されます。
onDocumentCreatedWithAuthContext
onDocumentWrittenWithAuthContext
onDocumentDeletedWithAuthContext
onDocumentUpdatedWithAuthContext
on_document_created_with_auth_context
on_document_updated_with_auth_context
on_document_deleted_with_auth_context
on_document_written_with_auth_context
認証コンテキストで使用可能なデータの情報については、認証コンテキストをご覧ください。次の例は、認証情報を取得する方法を示しています。
import { onDocumentWrittenWithAuthContext } from "firebase-functions/v2/firestore"
exports.syncUser = onDocumentWrittenWithAuthContext("users/{userId}", (event) => {
const snapshot = event.data.after;
if (!snapshot) {
console.log("No data associated with the event");
return;
}
const data = snapshot.data();
// retrieve auth context from event
const { authType, authId } = event;
let verified = false;
if (authType === "system") {
// system-generated users are automatically verified
verified = true;
} else if (authType === "unknown" || authType === "unauthenticated") {
// admin users from a specific domain are verified
if (authId.endsWith("@example.com")) {
verified = true;
}
}
return data.after.ref.set({
created_by: authId,
verified,
}, {merge: true});
});
@on_document_updated_with_auth_context(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
# Get the current and previous document values.
new_value = event.data.after
prev_value = event.data.before
# Get the auth context from the event
user_auth_type = event.auth_type
user_auth_id = event.auth_id
トリガー イベント外のデータ
Cloud Functions は信頼できる環境で実行されます。それらはプロジェクトのサービス アカウントとして認可されており、Firebase Admin SDK を使用して読み取りと書き込みを行うことができます。
const { initializeApp } = require('firebase-admin/app');
const { getFirestore, Timestamp, FieldValue } = require('firebase-admin/firestore');
initializeApp();
const db = getFirestore();
exports.writetofirestore = onDocumentWritten("some/doc", (event) => {
db.doc('some/otherdoc').set({ ... });
});
exports.writetofirestore = onDocumentWritten('users/{userId}', (event) => {
db.doc('some/otherdoc').set({
// Update otherdoc
});
});
from firebase_admin import firestore, initialize_app
import google.cloud.firestore
initialize_app()
@on_document_written(document="some/doc")
def myfunction(event: Event[Change[DocumentSnapshot | None]]) -> None:
firestore_client: google.cloud.firestore.Client = firestore.client()
firestore_client.document("another/doc").set({
# ...
})
制限事項
Cloud Functions の Cloud 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 を削除する
- 除外を使用してロギング自体を無効にすることもできますが、問題のあるイベントは引き続き配信されないことに注意してください。