Firestore ドキュメントで関数をトリガーする

このガイドでは、指定したコレクション内のドキュメントに変更を加えたときにトリガーされる関数の例を示します。

始める前に

このガイドのサンプルコードを実行する前に、次の作業を行う必要があります。

次の例は、Firestore トリガーに応答する関数の作成方法を示しています。

例 1: Hello Firestore 関数

次のサンプルは、トリガーされた Firestore イベントのフィールドを出力します。

Node.js

/**
 * 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

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)

Go


// 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

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());
  }
}

C#

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));
    }
}

Hello Firestore 関数をデプロイする

Firestore データベースをまだ設定していない場合は、設定します。

任意のツールを使用して、手順のタブでクリックします。

コンソール

Google Cloud コンソールを使用して関数を作成するときに、関数にトリガーを追加することもできます。関数のトリガーを作成する手順は次のとおりです。

  1. Google Cloud コンソールで、[Cloud Run] に移動します。

    Cloud Run に移動します

  2. [関数を作成] をクリックし、関数の詳細を入力します。デプロイ時に関数を構成する方法については、関数をデプロイするをご覧ください。

  3. [トリガー] セクションで [トリガーを追加] をクリックします。

  4. [Firestore トリガー] を選択します。

  5. [Eventarc トリガー] ペインで、トリガーの詳細を次のように変更します。

    1. [トリガー名] フィールドにトリガーの名前を入力するか、デフォルトの名前を使用します。

    2. リストからトリガーのタイプを選択して、次のいずれかのトリガータイプを指定します。

      • Google のソース: Pub/Sub、Cloud Storage、Firestore などの Google イベント プロバイダのトリガーを指定できます。

      • サードパーティ: Eventarc ソースを提供する Google 以外のプロバイダと統合できます。詳細については、Eventarc のサードパーティ イベントをご覧ください。

    3. [イベント プロバイダ] リストから [Firestore] を選択し、関数をトリガーするイベントのタイプを提供するプロダクトを選択します。イベント プロバイダのリストについては、イベント プロバイダと宛先をご覧ください。

    4. [イベントタイプ] リストから [type=google.cloud.firestore.document.v1.written] を選択します。トリガーの構成は、サポートされているイベントタイプによって異なります。詳細については、イベントタイプをご覧ください。

    5. [フィルタ] セクションで、データベース、オペレーション、属性の値を選択するか、デフォルト値を使用します。

    6. [リージョン] フィールドが有効になっている場合は、Eventarc トリガーのロケーションを選択します。一般に、Eventarc トリガーのロケーションは、イベントをモニタリングする Google Cloud リソースのロケーションと一致する必要があります。ほとんどの場合、Cloud Functions の関数を同じリージョンにデプロイする必要があります。Eventarc トリガーのロケーションの詳細については、Eventarc のロケーションについてをご覧ください。

    7. [サービス アカウント] フィールドで、サービス アカウントを選択します。Eventarc トリガーはサービス アカウントにリンクされ、関数を呼び出すときに ID として使用します。Eventarc トリガーのサービス アカウントには、関数を呼び出す権限が必要です。デフォルトでは、Cloud Run は Compute Engine のデフォルトのサービス アカウントを使用します。

    8. 受信リクエストの送信先であるサービスの URL パスを指定することもできます。これは、トリガーのイベントの送信先である宛先サービスの相対パスです。例: //routerouteroute/subroute.

  6. 必須フィールドに値を入力したら、[トリガーを保存] をクリックします。

gcloud

gcloud CLI を使用して関数を作成する場合は、まず関数をdeployしてからトリガーを作成する必要があります。関数のトリガーを作成する手順は次のとおりです。

  1. サンプルコードを含むディレクトリで次のコマンドを実行して、関数をデプロイします。

    gcloud beta run deploy FUNCTION \
            --source . \
            --function FUNCTION_ENTRYPOINT \
            --base-image BASE_IMAGE_ID \
            --region REGION
    

    次のように置き換えます。

    • FUNCTION: デプロイする関数の名前。このパラメータは省略できますが、省略すると名前の入力を求められます。

    • FUNCTION_ENTRYPOINT: ソースコード内の関数のエントリ ポイント。これは、関数の実行時に Cloud Run が実行するコードです。このフラグには、ソースコード内に存在する関数名または完全修飾クラス名を指定する必要があります。

    • BASE_IMAGE_ID: 関数のベースイメージ環境。ベースイメージと各イメージに含まれるパッケージの詳細については、ランタイム ベースイメージをご覧ください。

    • REGION: 関数をデプロイする Google Cloud リージョン。例: us-central1

  2. 次のコマンドを実行して、イベントをフィルタするトリガーを作成します。

    gcloud eventarc triggers create TRIGGER_NAME  \
        --location=EVENTARC_TRIGGER_LOCATION \
        --destination-run-service=FUNCTION  \
        --destination-run-region=REGION \
        --event-filters=type=google.cloud.firestore.document.v1.written \
        --event-filters=database='(default)' \
        --event-data-content-type=application/protobuf \
        --event-filters-path-pattern=document='users/{username}' \
        --service-account=PROJECT_NUMBER-compute@developer.gserviceaccount.com
    

    次のように置き換えます。

    • TRIGGER_NAME: トリガーの名前。

    • EVENTARC_TRIGGER_LOCATION: Eventarc トリガーのロケーション。一般に、Eventarc トリガーのロケーションは、イベントをモニタリングする Google Cloud リソースのロケーションと一致する必要があります。ほとんどの場合、関数も同じリージョンにデプロイする必要があります。詳細については、Eventarc のロケーションをご覧ください。

    • FUNCTION: デプロイする関数の名前。

    • REGION: 関数の Cloud Run リージョン

    • PROJECT_NUMBER は、Google Cloud プロジェクト番号に置き換えます。Eventarc トリガーはサービス アカウントにリンクされ、関数を呼び出すときに ID として使用します。Eventarc トリガーのサービス アカウントには、関数を呼び出す権限が必要です。デフォルトでは、Cloud Run はデフォルトのコンピューティング サービス アカウントを使用します。

    • event-filters フラグには、トリガーがモニタリングするイベント フィルタを指定します。event-filters フィルタにすべて一致するイベントが関数の呼び出しをトリガーします。各トリガーには、サポートされているイベントタイプを指定する必要があります。作成後にイベント フィルタのタイプを変更することはできません。イベント フィルタのタイプを変更するには、新しいトリガーを作成して古いトリガーを削除する必要があります。(省略可)さらにフィルタを追加するには、--event-filters フラグを繰り返し、サポートされているフィルタを ATTRIBUTE=VALUE の形式で指定します。

他のフィールドはそのまま使用します。

  • --event-filters=type=google.cloud.firestore.document.v1.written は、google.cloud.firestore.document.v1.written イベントタイプに従って、ドキュメントが作成、更新、削除されたとき関数がトリガーされるように指定します。
  • --event-filters=database='(default)' には Firebase データベースを指定します。デフォルトのデータベース名には (default) を使用します。
  • --event-filters-path-pattern=document='users/{username}' は、関連する変更をモニタリングするドキュメントのパスパターンを指定します。このパスパターンは、users コレクション内のすべてのドキュメントをモニタリングする必要があることを示しています。詳細については、パスパターンについてをご覧ください。

Hello Firestore 関数をテストする

Hello Firestore 関数をテストするには、Firestore データベースusers というコレクションを設定します。

  1. Google Cloud コンソールで、Firestore データベース ページに移動します。

    Firestore に移動

  2. [コレクションを開始] をクリックします。

  3. コレクション ID として users を指定します。

  4. コレクションの最初のドキュメントの追加を開始するには、[最初のドキュメントの追加] で、自動生成されたドキュメント ID を使用します。

  5. ドキュメントに少なくとも 1 つのフィールドを追加し、名前と値を指定します。たとえば、[フィールド名] に「username」、[フィールド値] に「rowan」と入力します。

  6. 完了したら [Save] をクリックします。

    この操作により新しいドキュメントが作成され、関数がトリガーされます。

  7. 関数がトリガーされたことを確認するには、Google Cloud コンソールの Cloud Run の概要ページで関数のリンク名をクリックして、[サービスの詳細] ページを開きます。

  8. [ログ] タブを選択して、次の文字列を探します。

Function triggered by change to: //firestore.googleapis.com/projects/your-project-id/databases/(default)'

例 2: Convert to Uppercase 関数

次の例では、ユーザーが追加した値を取得し、その場所にある文字列を大文字に変換して、大文字の文字列で置き換えます。

Node.js

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

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.")

Go


// 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

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();
  }
}

C#

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);
    }
}

Convert to Uppercase 関数をデプロイする

Firestore データベースをまだ設定していない場合は、設定します。

任意のツールを使用して、手順のタブでクリックします。

コンソール

Google Cloud コンソールを使用して関数を作成するときに、関数にトリガーを追加することもできます。関数のトリガーを作成する手順は次のとおりです。

  1. Google Cloud コンソールで、[Cloud Run] に移動します。

    Cloud Run に移動します

  2. [関数を作成] をクリックし、関数の詳細を入力します。デプロイ時に関数を構成する方法については、関数をデプロイするをご覧ください。

  3. [トリガー] セクションで [トリガーを追加] をクリックします。

  4. [Firestore トリガー] を選択します。

  5. [Eventarc トリガー] ペインで、トリガーの詳細を次のように変更します。

    1. [トリガー名] フィールドにトリガーの名前を入力するか、デフォルトの名前を使用します。

    2. リストからトリガーのタイプを選択して、次のいずれかのトリガータイプを指定します。

      • Google のソース: Pub/Sub、Cloud Storage、Firestore などの Google イベント プロバイダのトリガーを指定できます。

      • サードパーティ: Eventarc ソースを提供する Google 以外のプロバイダと統合できます。詳細については、Eventarc のサードパーティ イベントをご覧ください。

    3. [イベント プロバイダ] リストから [Firestore] を選択し、関数をトリガーするイベントのタイプを提供するプロダクトを選択します。イベント プロバイダのリストについては、イベント プロバイダと宛先をご覧ください。

    4. [イベントタイプ] リストから [type=google.cloud.firestore.document.v1.written] を選択します。トリガーの構成は、サポートされているイベントタイプによって異なります。詳細については、イベントタイプをご覧ください。

    5. [フィルタ] セクションで、データベース、オペレーション、属性の値を選択するか、デフォルト値を使用します。

    6. [リージョン] フィールドが有効になっている場合は、Eventarc トリガーのロケーションを選択します。一般に、Eventarc トリガーのロケーションは、イベントをモニタリングする Google Cloud リソースのロケーションと一致する必要があります。ほとんどの場合、Cloud Functions の関数を同じリージョンにデプロイする必要があります。Eventarc トリガーのロケーションの詳細については、Eventarc のロケーションについてをご覧ください。

    7. [サービス アカウント] フィールドで、サービス アカウントを選択します。Eventarc トリガーはサービス アカウントにリンクされ、関数を呼び出すときに ID として使用します。Eventarc トリガーのサービス アカウントには、関数を呼び出す権限が必要です。デフォルトでは、Cloud Run は Compute Engine のデフォルトのサービス アカウントを使用します。

    8. 受信リクエストの送信先であるサービスの URL パスを指定することもできます。これは、トリガーのイベントの送信先である宛先サービスの相対パスです。例: //routerouteroute/subroute.

  6. 必須フィールドに値を入力したら、[トリガーを保存] をクリックします。

gcloud

gcloud CLI を使用して関数を作成する場合は、まず関数をdeployしてからトリガーを作成する必要があります。関数のトリガーを作成する手順は次のとおりです。

  1. サンプルコードを含むディレクトリで次のコマンドを実行して、関数をデプロイします。

    gcloud beta run deploy FUNCTION \
            --source . \
            --function FUNCTION_ENTRYPOINT \
            --base-image BASE_IMAGE_ID \
            --region REGION
    

    次のように置き換えます。

    • FUNCTION: デプロイする関数の名前。このパラメータは省略できますが、省略すると名前の入力を求められます。

    • FUNCTION_ENTRYPOINT: ソースコード内の関数のエントリ ポイント。これは、関数の実行時に Cloud Run が実行するコードです。このフラグには、ソースコード内に存在する関数名または完全修飾クラス名を指定する必要があります。

    • BASE_IMAGE_ID: 関数のベースイメージ環境。ベースイメージと各イメージに含まれるパッケージの詳細については、ランタイム ベースイメージをご覧ください。

    • REGION: 関数をデプロイする Google Cloud リージョン。例: us-central1

  2. 次のコマンドを実行して、イベントをフィルタするトリガーを作成します。

    gcloud eventarc triggers create TRIGGER_NAME  \
        --location=EVENTARC_TRIGGER_LOCATION \
        --destination-run-service=FUNCTION  \
        --destination-run-region=REGION \
        --event-filters=type=google.cloud.firestore.document.v1.written \
        --event-filters=database='(default)' \
        --event-data-content-type=application/protobuf \
        --event-filters-path-pattern=document='messages/{pushId}' \
        --service-account=PROJECT_NUMBER-compute@developer.gserviceaccount.com
    

    次のように置き換えます。

    • TRIGGER_NAME: トリガーの名前。

    • EVENTARC_TRIGGER_LOCATION: Eventarc トリガーのロケーション。一般に、Eventarc トリガーのロケーションは、イベントをモニタリングする Google Cloud リソースのロケーションと一致する必要があります。ほとんどの場合、関数も同じリージョンにデプロイする必要があります。詳細については、Eventarc のロケーションをご覧ください。

    • FUNCTION: デプロイする関数の名前。

    • REGION: 関数の Cloud Run リージョン

    • PROJECT_NUMBER は、Google Cloud プロジェクト番号に置き換えます。Eventarc トリガーはサービス アカウントにリンクされ、関数を呼び出すときに ID として使用します。Eventarc トリガーのサービス アカウントには、関数を呼び出す権限が必要です。デフォルトでは、Cloud Run はデフォルトのコンピューティング サービス アカウントを使用します。

    • event-filters フラグには、トリガーがモニタリングするイベント フィルタを指定します。event-filters フィルタにすべて一致するイベントが関数の呼び出しをトリガーします。各トリガーには、サポートされているイベントタイプを指定する必要があります。作成後にイベント フィルタのタイプを変更することはできません。イベント フィルタのタイプを変更するには、新しいトリガーを作成して古いトリガーを削除する必要があります。(省略可)さらにフィルタを追加するには、--event-filters フラグを繰り返し、サポートされているフィルタを ATTRIBUTE=VALUE の形式で指定します。

他のフィールドはそのまま使用します。

  • --event-filters=type=google.cloud.firestore.document.v1.written は、google.cloud.firestore.document.v1.written イベントタイプに従って、ドキュメントが作成、更新、削除されたときに関数がトリガーされるように指定します。
  • --event-filters=database='(default)' には、Firestore データベースを指定します。デフォルトのデータベース名には (default) を使用します。
  • --event-filters-path-pattern=document='messages/{pushId}' は、関連する変更をモニタリングするドキュメントのパスパターンを指定します。このパスパターンは、messages コレクション内のすべてのドキュメントをモニタリングする必要があることを示しています。詳細については、パスパターンについてをご覧ください。

Convert to Uppercase 関数をテストする

デプロイした Convert to Uppercase 関数をテストするには、Firestore データベースmessages というコレクションを設定します。

  1. Google Cloud コンソールで、Firestore データベース ページに移動します。

    Firestore に移動

  2. [コレクションを開始] をクリックします。

  3. コレクション ID として messages を指定します。

  4. コレクションの最初のドキュメントの追加を開始するには、[最初のドキュメントの追加] で、自動生成されたドキュメント ID を使用します。

  5. デプロイされた関数をトリガーするには、フィールド名originalフィールド値minka のドキュメントを追加します。

  6. ドキュメントを保存すると、値フィールドの小文字の単語が大文字に変換されます。

    その後、フィールド値を編集して小文字を含めると、関数が再度トリガーされ、すべての小文字が大文字に変換されます。

関数の制限事項

  • 順序は保証されません。短時間に複数の変更を行うと、予期しない順序で関数の呼び出しがトリガーされることがあります。
  • イベントは必ず 1 回以上処理されますが、1 つのイベントで関数が複数回呼び出される場合があります。「正確に 1 回」のメカニズムに依存することは避け、べき等になるように関数を記述してください。
  • トリガーは、単一のデータベースに関連付けられます。複数のデータベースに一致するトリガーは作成できません。
  • データベースを削除しても、そのデータベースのトリガーは自動的に削除されません。トリガーはイベントの配信を停止しますが、トリガーを削除するまで存在し続けます。