Cloud Run functions(第 2 世代)で拡張する

Cloud Run functions を使用してコードをデプロイすることで、Firestore データベースの変更によってトリガーされるイベントを処理できます。これにより、独自のサーバーを実行しなくても、サーバー側の機能を追加できます。

Cloud Run functions(第 2 世代)を使用して Firestore を拡張する

Cloud Run functions(第 2 世代)では、次の Firestore イベント トリガーをサポートして、Firestore イベントに関連付けられたハンドラを作成できます。

イベントタイプ トリガー
google.cloud.firestore.document.v1.created ドキュメントが最初に書き込まれたときにトリガーされます。
google.cloud.firestore.document.v1.updated すでに存在するドキュメントの値が変更されたときにトリガーされます。
google.cloud.firestore.document.v1.deleted ドキュメントが削除されたときにトリガーされます。
google.cloud.firestore.document.v1.written createdupdated または deleted がトリガーされたときにトリガーされます。
google.cloud.firestore.document.v1.created.withAuthContext created と同じですが、認証情報を追加します。
google.cloud.firestore.document.v1.updated.withAuthContext updated と同じですが、認証情報を追加します。
google.cloud.firestore.document.v1.deleted.withAuthContext deleted と同じですが、認証情報を追加します。
google.cloud.firestore.document.v1.written.withAuthContext written と同じですが、認証情報を追加します。

Firestore のイベントは、ドキュメントが変更された場合にのみトリガーされます。Firestore ドキュメントの更新でデータが変更されない場合(オペレーションなしの書き込み)、更新イベントや書き込みイベントは生成されません。特定のフィールドにイベントを追加することはできません。

イベントに認証コンテキストを含める

イベントに関する追加の認証情報を含めるには、withAuthContext 拡張機能とともにイベント トリガーを使用します。この拡張機能は、イベントをトリガーしたプリンシパルに関する追加情報を追加します。ベースイベントで返される情報に加えて、authtype 属性と authid 属性を追加します。属性値の詳細については、authcontext のリファレンスをご覧ください。

Firestore でトリガーされる関数を作成する

Firestore イベントに応答する関数を作成するには、デプロイ中に次の内容を指定する準備を行います。

  • トリガー イベントのタイプ
  • 関数に関連付けられたドキュメントを選択するトリガー イベント フィルタ
  • 実行する関数コード

トリガー イベント フィルタ

イベント フィルタを指定するときに、ドキュメントの完全一致またはパスパターンを指定できます。パスパターンを使用して、ワイルドカード * または ** を使用して複数のドキュメントに一致させます。

たとえば、次のドキュメントへの変更に応答できます。

users/marie

パターンに一致するドキュメントの変更に対応するには、ワイルドカード * または ** を使用します。ワイルドカード * は単一のセグメントに一致し、マルチセグメント ワイルドカード ** はパターン内の 0 個以上のセグメントに一致します。

単一セグメントの一致(*)の場合は、名前付きキャプチャ グループを使用することもできます。例: users/{userId}

例:

パターン 説明
/users/* または /users/{userId} /users コレクション内のすべてのドキュメントに一致します。/users/marie/messages/33e2IxYBD9enzS50SJ68 のようなサブコレクションのドキュメントと一致しません
/users/** /users コレクション内のすべてのドキュメントと、/users/marie/messages/33e2IxYBD9enzS50SJ68 などのサブコレクションのドキュメントと一致します。

パスパターンの詳細については、Eventarc パスのパターンをご覧ください。

ワイルドカードを使用する場合でも、トリガーは常にドキュメントを指している必要があります。たとえば、{messageCollectionId=*} はコレクションであるため、users/{userId=*}/{messageCollectionId=*} は無効になります。一方で、{messageId=*} は常にドキュメントを指すため、users/{userId=*}/{messageCollectionId}/{messageId=*} は有効です。

関数の例

次のサンプルは、Firestore イベントを受信する方法を示しています。イベントに関連するドキュメント データを操作するには、value フィールドと old_value フィールドを確認します。

  • value: オペレーション後のドキュメント スナップショットを含む Document オブジェクト。このフィールドは、削除イベントについては入力されません。
  • old_value: オペレーション前のドキュメント スナップショットを含む Document オブジェクト。このフィールドは、更新イベントと削除イベントに対してのみ入力されます。

Go

Firestore に対する認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証の設定をご覧ください。


// Package hellofirestore contains a Cloud Event Function triggered by a Cloud Firestore event.
package hellofirestore

import (
	"context"
	"fmt"

	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
	"github.com/cloudevents/sdk-go/v2/event"
	"github.com/googleapis/google-cloudevents-go/cloud/firestoredata"
	"google.golang.org/protobuf/proto"
)

func init() {
	functions.CloudEvent("helloFirestore", HelloFirestore)
}

// HelloFirestore is triggered by a change to a Firestore document.
func HelloFirestore(ctx context.Context, event event.Event) error {
	var data firestoredata.DocumentEventData

	// If you omit `DiscardUnknown`, protojson.Unmarshal returns an error
	// when encountering a new or unknown field.
	options := proto.UnmarshalOptions{
		DiscardUnknown: true,
	}
	err := options.Unmarshal(event.Data(), &data)

	if err != nil {
		return fmt.Errorf("proto.Unmarshal: %w", err)
	}

	fmt.Printf("Function triggered by change to: %v\n", event.Source())
	fmt.Printf("Old value: %+v\n", data.GetOldValue())
	fmt.Printf("New value: %+v\n", data.GetValue())
	return nil
}

Java

Firestore に対する認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証の設定をご覧ください。

import com.google.cloud.functions.CloudEventsFunction;
import com.google.events.cloud.firestore.v1.DocumentEventData;
import com.google.protobuf.InvalidProtocolBufferException;
import io.cloudevents.CloudEvent;
import java.util.logging.Logger;

public class FirebaseFirestore implements CloudEventsFunction {
  private static final Logger logger = Logger.getLogger(FirebaseFirestore.class.getName());

  @Override
  public void accept(CloudEvent event) throws InvalidProtocolBufferException {
    DocumentEventData firestoreEventData = DocumentEventData
        .parseFrom(event.getData().toBytes());

    logger.info("Function triggered by event on: " + event.getSource());
    logger.info("Event type: " + event.getType());

    logger.info("Old value:");
    logger.info(firestoreEventData.getOldValue().toString());

    logger.info("New value:");
    logger.info(firestoreEventData.getValue().toString());
  }
}

Node.js

Firestore に対する認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証の設定をご覧ください。

protobufjs を使用してイベントデータをデコードします。ソースに google.events.cloud.firestore.v1 data.proto を含めます。
/**
 * Cloud Event Function triggered by a change to a Firestore document.
 */
const functions = require('@google-cloud/functions-framework');
const protobuf = require('protobufjs');

functions.cloudEvent('helloFirestore', async cloudEvent => {
  console.log(`Function triggered by event on: ${cloudEvent.source}`);
  console.log(`Event type: ${cloudEvent.type}`);

  console.log('Loading protos...');
  const root = await protobuf.load('data.proto');
  const DocumentEventData = root.lookupType(
    'google.events.cloud.firestore.v1.DocumentEventData'
  );

  console.log('Decoding data...');
  const firestoreReceived = DocumentEventData.decode(cloudEvent.data);

  console.log('\nOld value:');
  console.log(JSON.stringify(firestoreReceived.oldValue, null, 2));

  console.log('\nNew value:');
  console.log(JSON.stringify(firestoreReceived.value, null, 2));
});

Python

Firestore に対する認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証の設定をご覧ください。

from cloudevents.http import CloudEvent
import functions_framework
from google.events.cloud import firestore


@functions_framework.cloud_event
def hello_firestore(cloud_event: CloudEvent) -> None:
    """Triggers by a change to a Firestore document.

    Args:
        cloud_event: cloud event with information on the firestore event trigger
    """
    firestore_payload = firestore.DocumentEventData()
    firestore_payload._pb.ParseFromString(cloud_event.data)

    print(f"Function triggered by change to: {cloud_event['source']}")

    print("\nOld value:")
    print(firestore_payload.old_value)

    print("\nNew value:")
    print(firestore_payload.value)

C#

Firestore に対する認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証の設定をご覧ください。

using CloudNative.CloudEvents;
using Google.Cloud.Functions.Framework;
using Google.Events.Protobuf.Cloud.Firestore.V1;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace FirebaseFirestore;

public class Function : ICloudEventFunction<DocumentEventData>
{
    private readonly ILogger _logger;

    public Function(ILogger<Function> logger) =>
        _logger = logger;

    public Task HandleAsync(CloudEvent cloudEvent, DocumentEventData data, CancellationToken cancellationToken)
    {
        _logger.LogInformation("Function triggered by event on {subject}", cloudEvent.Subject);
        _logger.LogInformation("Event type: {type}", cloudEvent.Type);
        MaybeLogDocument("Old value", data.OldValue);
        MaybeLogDocument("New value", data.Value);

        // In this example, we don't need to perform any asynchronous operations, so the
        // method doesn't need to be declared async.
        return Task.CompletedTask;
    }

    /// <summary>
    /// Logs the names and values of the fields in a document in a very simplistic way.
    /// </summary>
    private void MaybeLogDocument(string message, Document document)
    {
        if (document is null)
        {
            return;
        }

        // ConvertFields converts the Firestore representation into a .NET-friendly
        // representation.
        IReadOnlyDictionary<string, object> fields = document.ConvertFields();
        var fieldNamesAndTypes = fields
            .OrderBy(pair => pair.Key)
            .Select(pair => $"{pair.Key}: {pair.Value}");
        _logger.LogInformation(message + ": {fields}", string.Join(", ", fieldNamesAndTypes));
    }
}

次の例では、影響を受けるドキュメントの original フィールドに追加された文字列を大文字に変換し、新しい値を同じドキュメントに書き込みます。

Go

Firestore に対する認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証の設定をご覧ください。


// Package upper contains a Firestore Cloud Function.
package upper

import (
	"context"
	"errors"
	"fmt"
	"log"
	"os"
	"strings"

	"cloud.google.com/go/firestore"
	firebase "firebase.google.com/go/v4"
	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
	"github.com/cloudevents/sdk-go/v2/event"
	"github.com/googleapis/google-cloudevents-go/cloud/firestoredata"
	"google.golang.org/protobuf/proto"
)

// set the GOOGLE_CLOUD_PROJECT environment variable when deploying.
var projectID = os.Getenv("GOOGLE_CLOUD_PROJECT")

// client is a Firestore client, reused between function invocations.
var client *firestore.Client

func init() {
	// Use the application default credentials.
	conf := &firebase.Config{ProjectID: projectID}

	// Use context.Background() because the app/client should persist across
	// invocations.
	ctx := context.Background()

	app, err := firebase.NewApp(ctx, conf)
	if err != nil {
		log.Fatalf("firebase.NewApp: %v", err)
	}

	client, err = app.Firestore(ctx)
	if err != nil {
		log.Fatalf("app.Firestore: %v", err)
	}

	// Register cloud event function
	functions.CloudEvent("MakeUpperCase", MakeUpperCase)
}

// MakeUpperCase is triggered by a change to a Firestore document. It updates
// the `original` value of the document to upper case.
func MakeUpperCase(ctx context.Context, e event.Event) error {
	var data firestoredata.DocumentEventData

	// If you omit `DiscardUnknown`, protojson.Unmarshal returns an error
	// when encountering a new or unknown field.
	options := proto.UnmarshalOptions{
		DiscardUnknown: true,
	}
	err := options.Unmarshal(e.Data(), &data)

	if err != nil {
		return fmt.Errorf("proto.Unmarshal: %w", err)
	}

	if data.GetValue() == nil {
		return errors.New("Invalid message: 'Value' not present")
	}

	fullPath := strings.Split(data.GetValue().GetName(), "/documents/")[1]
	pathParts := strings.Split(fullPath, "/")
	collection := pathParts[0]
	doc := strings.Join(pathParts[1:], "/")

	var originalStringValue string
	if v, ok := data.GetValue().GetFields()["original"]; ok {
		originalStringValue = v.GetStringValue()
	} else {
		return errors.New("Document did not contain field \"original\"")
	}

	newValue := strings.ToUpper(originalStringValue)
	if originalStringValue == newValue {
		log.Printf("%q is already upper case: skipping", originalStringValue)
		return nil
	}
	log.Printf("Replacing value: %q -> %q", originalStringValue, newValue)

	newDocumentEntry := map[string]string{"original": newValue}
	_, err = client.Collection(collection).Doc(doc).Set(ctx, newDocumentEntry)
	if err != nil {
		return fmt.Errorf("Set: %w", err)
	}
	return nil
}

Java

Firestore に対する認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証の設定をご覧ください。

import com.google.cloud.firestore.Firestore;
import com.google.cloud.firestore.FirestoreOptions;
import com.google.cloud.firestore.SetOptions;
import com.google.cloud.functions.CloudEventsFunction;
import com.google.events.cloud.firestore.v1.DocumentEventData;
import com.google.events.cloud.firestore.v1.Value;
import com.google.protobuf.InvalidProtocolBufferException;
import io.cloudevents.CloudEvent;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;

public class FirebaseFirestoreReactive implements CloudEventsFunction {
  private static final Logger logger = Logger.getLogger(FirebaseFirestoreReactive.class.getName());
  private final Firestore firestore;

  private static final String FIELD_KEY = "original";
  private static final String APPLICATION_PROTOBUF = "application/protobuf";

  public FirebaseFirestoreReactive() {
    this(FirestoreOptions.getDefaultInstance().getService());
  }

  public FirebaseFirestoreReactive(Firestore firestore) {
    this.firestore = firestore;
  }

  @Override
  public void accept(CloudEvent event)
      throws InvalidProtocolBufferException, InterruptedException, ExecutionException {
    if (event.getData() == null) {
      logger.warning("No data found in event!");
      return;
    }

    if (!event.getDataContentType().equals(APPLICATION_PROTOBUF)) {
      logger.warning(String.format("Found unexpected content type %s, expected %s",
          event.getDataContentType(),
          APPLICATION_PROTOBUF));
      return;
    }

    DocumentEventData firestoreEventData = DocumentEventData
        .parseFrom(event.getData().toBytes());

    // Get the fields from the post-operation document snapshot
    // https://firebase.google.com/docs/firestore/reference/rest/v1/projects.databases.documents#Document
    Map<String, Value> fields = firestoreEventData.getValue().getFieldsMap();
    if (!fields.containsKey(FIELD_KEY)) {
      logger.warning("Document does not contain original field");
      return;
    }
    String currValue = fields.get(FIELD_KEY).getStringValue();
    String newValue = currValue.toUpperCase();

    if (currValue.equals(newValue)) {
      logger.info("Value is already upper-case");
      return;
    }

    // Retrieve the document name from the resource path:
    // projects/{project_id}/databases/{database_id}/documents/{document_path}
    String affectedDoc = firestoreEventData.getValue()
        .getName()
        .split("/documents/")[1]
        .replace("\"", "");

    logger.info(String.format("Replacing values: %s --> %s", currValue, newValue));

    // Wait for the async call to complete
    this.firestore
        .document(affectedDoc)
        .set(Map.of(FIELD_KEY, newValue), SetOptions.merge())
        .get();
  }
}

Node.js

Firestore に対する認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証の設定をご覧ください。

protobufjs を使用してイベントデータをデコードします。ソースに google.events.cloud.firestore.v1 data.proto を含めます。
const functions = require('@google-cloud/functions-framework');
const Firestore = require('@google-cloud/firestore');
const protobuf = require('protobufjs');

const firestore = new Firestore({
  projectId: process.env.GOOGLE_CLOUD_PROJECT,
});

// Converts strings added to /messages/{pushId}/original to uppercase
functions.cloudEvent('makeUpperCase', async cloudEvent => {
  console.log('Loading protos...');
  const root = await protobuf.load('data.proto');
  const DocumentEventData = root.lookupType(
    'google.events.cloud.firestore.v1.DocumentEventData'
  );

  console.log('Decoding data...');
  const firestoreReceived = DocumentEventData.decode(cloudEvent.data);

  const resource = firestoreReceived.value.name;
  const affectedDoc = firestore.doc(resource.split('/documents/')[1]);

  const curValue = firestoreReceived.value.fields.original.stringValue;
  const newValue = curValue.toUpperCase();

  if (curValue === newValue) {
    // Value is already upper-case
    // Don't perform a(nother) write to avoid infinite loops
    console.log('Value is already upper-case.');
    return;
  }

  console.log(`Replacing value: ${curValue} --> ${newValue}`);
  affectedDoc.set({
    original: newValue,
  });
});

Python

Firestore に対する認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証の設定をご覧ください。

from cloudevents.http import CloudEvent
import functions_framework
from google.cloud import firestore
from google.events.cloud import firestore as firestoredata

client = firestore.Client()


# Converts strings added to /messages/{pushId}/original to uppercase
@functions_framework.cloud_event
def make_upper_case(cloud_event: CloudEvent) -> None:
    firestore_payload = firestoredata.DocumentEventData()
    firestore_payload._pb.ParseFromString(cloud_event.data)

    path_parts = firestore_payload.value.name.split("/")
    separator_idx = path_parts.index("documents")
    collection_path = path_parts[separator_idx + 1]
    document_path = "/".join(path_parts[(separator_idx + 2) :])

    print(f"Collection path: {collection_path}")
    print(f"Document path: {document_path}")

    affected_doc = client.collection(collection_path).document(document_path)

    cur_value = firestore_payload.value.fields["original"].string_value
    new_value = cur_value.upper()

    if cur_value != new_value:
        print(f"Replacing value: {cur_value} --> {new_value}")
        affected_doc.set({"original": new_value})
    else:
        # Value is already upper-case
        # Don't perform a second write (which can trigger an infinite loop)
        print("Value is already upper-case.")

C#

Firestore に対する認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証の設定をご覧ください。

using CloudNative.CloudEvents;
using Google.Cloud.Firestore;
using Google.Cloud.Functions.Framework;
using Google.Cloud.Functions.Hosting;
using Google.Events.Protobuf.Cloud.Firestore.V1;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace FirestoreReactive;

public class Startup : FunctionsStartup
{
    public override void ConfigureServices(WebHostBuilderContext context, IServiceCollection services) =>
        services.AddSingleton(FirestoreDb.Create());
}

// Register the startup class to provide the Firestore dependency.
[FunctionsStartup(typeof(Startup))]
public class Function : ICloudEventFunction<DocumentEventData>
{
    private readonly ILogger _logger;
    private readonly FirestoreDb _firestoreDb;

    public Function(ILogger<Function> logger, FirestoreDb firestoreDb) =>
        (_logger, _firestoreDb) = (logger, firestoreDb);

    public async Task HandleAsync(CloudEvent cloudEvent, DocumentEventData data, CancellationToken cancellationToken)
    {
        // Get the recently-written value. This expression will result in a null value
        // if any of the following is true:
        // - The event doesn't contain a "new" document
        // - The value doesn't contain a field called "original"
        // - The "original" field isn't a string
        string currentValue = data.Value?.ConvertFields().GetValueOrDefault("original") as string;
        if (currentValue is null)
        {
            _logger.LogWarning($"Event did not contain a suitable document");
            return;
        }

        string newValue = currentValue.ToUpperInvariant();
        if (newValue == currentValue)
        {
            _logger.LogInformation("Value is already upper-cased; no replacement necessary");
            return;
        }

        // The CloudEvent subject is "documents/x/y/...".
        // The Firestore SDK FirestoreDb.Document method expects a reference relative to
        // "documents" (so just the "x/y/..." part). This may be simplified over time.
        if (cloudEvent.Subject is null || !cloudEvent.Subject.StartsWith("documents/"))
        {
            _logger.LogWarning("CloudEvent subject is not a document reference.");
            return;
        }
        string documentPath = cloudEvent.Subject.Substring("documents/".Length);

        _logger.LogInformation("Replacing '{current}' with '{new}' in '{path}'", currentValue, newValue, documentPath);
        await _firestoreDb.Document(documentPath).UpdateAsync("original", newValue, cancellationToken: cancellationToken);
    }
}

proto 依存関係をソースに含める

関数のソース ディレクトリに Firestore data.proto ファイルを含める必要があります。このファイルは、ソース ディレクトリにも含める必要がある次の proto をインポートします。

依存関係に同じディレクトリ構造を使用します。たとえば、struct.protogoogle/protobuf 内に配置します。

これらのファイルは、イベントデータをデコードするのに必要です。関数のソースにこれらのファイルが含まれていない場合は、実行時にエラーが返されます。

イベント属性

各イベントには、イベントがトリガーされた時間など、イベントに関する情報を含むデータ属性が含まれます。Firestore は、イベントに関連するデータベースとドキュメントに関するデータを追加します。これらの属性には、次のようにアクセスできます。

Java
logger.info("Function triggered by event on: " + event.getSource());
logger.info("Event type: " + event.getType());
logger.info("Event time " + event.getTime());
logger.info("Event project: " + event.getExtension("project"));
logger.info("Event location: " + event.getExtension("location"));
logger.info("Database name: " + event.getExtension("database"));
logger.info("Database document: " + event.getExtension("document"));
// For withAuthContext events
logger.info("Auth information: " + event.getExtension("authid"));
logger.info("Auth information: " + event.getExtension("authtype"));
Node.js
console.log(`Function triggered by event on: ${cloudEvent.source}`);
console.log(`Event type: ${cloudEvent.type}`);
console.log(`Event time: ${cloudEvent.time}`);
console.log(`Event project: ${cloudEvent.project}`);
console.log(`Event location: ${cloudEvent.location}`);
console.log(`Database name: ${cloudEvent.database}`);
console.log(`Document name: ${cloudEvent.document}`);
// For withAuthContext events
console.log(`Auth information: ${cloudEvent.authid}`);
console.log(`Auth information: ${cloudEvent.authtype}`);
Python
print(f"Function triggered by change to: {cloud_event['source']}")
print(f"Event type: {cloud_event['type']}")
print(f"Event time: {cloud_event['time']}")
print(f"Event project: {cloud_event['project']}")
print(f"Location: {cloud_event['location']}")
print(f"Database name: {cloud_event['database']}")
print(f"Document: {cloud_event['document']}")
// For withAuthContext events
print(f"Auth information: {cloud_event['authid']}")
print(f"Auth information: {cloud_event['authtype']}")

関数をデプロイする

Cloud Run functions をデプロイするユーザーには、Cloud Run functions デベロッパーの IAM ロールまたは同等の権限を含むロールが必要です。デプロイの追加構成もご覧ください。

関数をデプロイするには、gcloud CLI または Google Cloud コンソールを使用します。以下の例は、gcloud CLI を使用したデプロイを示しています。Google Cloud コンソールを使用したデプロイの詳細については、Cloud Run 関数をデプロイするをご覧ください。

gcloud

  1. In the Google Cloud console, activate Cloud Shell.

    Activate Cloud Shell

    At the bottom of the Google Cloud console, a Cloud Shell session starts and displays a command-line prompt. Cloud Shell is a shell environment with the Google Cloud CLI already installed and with values already set for your current project. It can take a few seconds for the session to initialize.

  2. gcloud functions deploy コマンドを使用して、関数をデプロイします。

    gcloud functions deploy YOUR_FUNCTION_NAME \
    --gen2 \
    --region=FUNCTION_LOCATION \
    --trigger-location=TRIGGER_LOCATION \
    --runtime=YOUR_RUNTIME \
    --source=YOUR_SOURCE_LOCATION \
    --entry-point=YOUR_CODE_ENTRYPOINT \
    --trigger-event-filters="type=EVENT_FILTER_TYPE" \
    --trigger-event-filters="database=DATABASE" \
    --trigger-event-filters-path-pattern="document=DOCUMENT" \
    

    最初の引数 YOUR_FUNCTION_NAME は、デプロイされた関数の名前です。関数名は、先頭を文字にし、その後に 62 文字以下の文字、数字、ハイフン、アンダースコアを続けます。末尾は文字または数字にする必要があります。

    • --gen2 フラグは、Cloud Run 関数(第 2 世代)にデプロイすることを指定します。このフラグを省略すると、Cloud Run 関数(第 1 世代)にデプロイされます。

    • --region フラグには、関数をデプロイするリージョンを指定します。

      近接性を最大化するには、Firestore データベースの近くのリージョンに設定します。Firestore データベースがマルチリージョン ロケーションにある場合、nam5 のデータベースの場合は us-central1 に、eur3 のデータベースの場合は europe-west4 に設定します。リージョン Firestore のロケーションの場合は、同じリージョンに設定します。

    • --trigger-location フラグには、トリガーのロケーションを指定します。このフラグを Firestore データベースのロケーションに設定する必要があります。

    • --runtime フラグには、関数で使用される言語ランタイムを指定します。Cloud Run functions は、複数のランタイムをサポートしています。詳細については、ランタイムをご覧ください。

    • --source フラグには、関数のソースコードの場所を指定します。詳細については、以下をご覧ください。

    • --entry-point フラグには、ソースコード内の関数のエントリ ポイントを指定します。これは、関数の実行時に実行されるコードです。このフラグには、ソースコード内に存在する関数名または完全修飾クラス名を指定する必要があります。詳細については、関数のエントリ ポイントをご覧ください。

    • EVENT_FILTER_TYPE: Firestore は、次のイベントタイプをサポートしています。

      • google.cloud.firestore.document.v1.created: ドキュメントが最初に書き込まれたときにイベントが送信されます。
      • google.cloud.firestore.document.v1.updated: ドキュメントがすでに存在し、値が変更されたときに、イベントが送信されます。
      • google.cloud.firestore.document.v1.deleted: ドキュメントが削除されたときにイベントが送信されます。
      • google.cloud.firestore.document.v1.written: ドキュメントが作成、更新、または削除されたときにイベントが送信されます。
      • google.cloud.firestore.document.v1.created.withAuthContext: ドキュメントが最初に書き込まれたときにイベントが送信されます。このイベントには追加の認証情報が含まれています。
      • google.cloud.firestore.document.v1.updated.withAuthContext: ドキュメントがすでに存在し、値が変更されたときに、イベントが送信されます。追加の認証情報が含まれています
      • google.cloud.firestore.document.v1.deleted.withAuthContext: ドキュメントが削除されたときにイベントが送信されます。追加の認証情報が含まれています
      • google.cloud.firestore.document.v1.written.withAuthContext: ドキュメントが作成、更新、または削除されたときにイベントが送信されます。追加の認証情報が含まれています
    • DATABASE: Firestore データベース。デフォルトのデータベース名には、(default) を使用します。

    • DOCUMENT: データが作成、更新、削除されたときにイベントをトリガーするデータベース パス。演算子には次のいずれかを指定できます。

      • 等しい。例: --trigger-event-filters=document='users/marie'
      • 経路パターン。例: --trigger-event-filters-path-pattern=document='users/*'詳細については、パスパターンについてをご覧ください。

    関数をデプロイするときに、追加の構成ネットワーキングセキュリティのオプションを指定することもできます。

    デプロイ コマンドとそのフラグの詳細については、gcloud functions deploy のドキュメントをご覧ください。

デプロイの例

次の例は、Google Cloud CLI を使用したデプロイを示しています。

us-west2 リージョンにデータベースの関数をデプロイします。

gcloud functions deploy gcfv2-trigger-firestore-node \
--gen2 \
--region=us-west2 \
--trigger-location=us-west2 \
--runtime=nodejs18 \
--source=gs://CLOUD_STORAGE_BUCKET/firestoreEventFunction.zip \
--entry-point=makeUpperCase \
--trigger-event-filters=type=google.cloud.firestore.document.v1.written \
--trigger-event-filters=database='(default)' \
--trigger-event-filters-path-pattern=document='messages/{pushId}'

nam5 マルチリージョンにデータベースの関数をデプロイします。

gcloud functions deploy gcfv2-trigger-firestore-python \
--gen2 \
--region=us-central1 \
--trigger-location=nam5 \
--runtime=python311 \
--source=gs://CLOUD_STORAGE_BUCKET/firestoreEventFunction.zip \
--entry-point=make_upper_case \
--trigger-event-filters=type=google.cloud.firestore.document.v1.written.withAuthContext \
--trigger-event-filters=database='(default)' \
--trigger-event-filters-path-pattern=document='messages/{pushId}'

制限事項

Cloud Run 関数の Firestore トリガーには、次の制限事項があります。

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

Eventarc と Firestore のロケーション

Eventarc は、Firestore イベント トリガーのマルチリージョンをサポートしていませんが、マルチリージョン ロケーションの Firestore データベースのトリガーは作成できます。Eventarc は、Firestore マルチリージョン ロケーションを次の Eventarc リージョンにマッピングします。

Firestore マルチリージョン Eventarc リージョン
nam5 us-central1
eur3 europe-west4

Cloud Run 関数の第 2 世代と第 1 世代の違い

Cloud Run 関数(第 2 世代)では、すべてのランタイムに Eventarc イベントを使用します。以前は、Cloud Run 関数(第 1 世代)では、一部のランタイムのみに Eventarc イベントが使用されていました。Eventarc イベントでは、Cloud Run 関数(第 1 世代)と次の点が異なります。

  • Eventarc 用の Firestore トリガーは、Cloud Run 関数以外の追加の宛先をサポートしています。CloudEvents は、Cloud Run、GKE、Workflows など、さまざまな宛先に転送できます。

  • Eventarc の Firestore トリガーは、データベース書き込みオペレーションの開始時にトリガー定義を取得し、その定義を使用して Firestore がイベントを出力するかどうかを判断します。書き込みオペレーションでは、実行時に発生する可能性のある定義の変更を考慮することはありません。

    Cloud Run 関数(第 1 世代)は、データベース書き込みの評価中にトリガー定義を取得し、評価中のトリガーの変更は、Firestore がイベントを出力するかどうかに影響します。

詳細については、Cloud Run functions のバージョンの比較をご覧ください。