使用 Firestore 文件觸發函式

只要建立、更新或刪除指定集合中的文件,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 控制台、Google Cloud CLI 或 Terraform 設定觸發條件。

控制台

使用 Google Cloud 控制台建立函式時,也可以為函式新增觸發條件。請按照下列步驟為函式建立觸發條件:

  1. 前往 Google Cloud 控制台的「Cloud Run」頁面:

    前往 Cloud Run。

  2. 按一下「編寫函式」,然後輸入函式詳細資料。如要進一步瞭解如何在部署期間設定函式,請參閱「部署函式」。

  3. 在「觸發條件」部分,按一下「新增觸發條件」。

  4. 選取「Firestore trigger」(Firestore 觸發條件)。

  5. 在「Eventarc trigger」(Eventarc 觸發條件) 窗格中,按照下列方式修改觸發條件詳細資料:

    1. 在「Trigger name」(觸發條件名稱) 欄位中輸入觸發條件名稱,或使用預設名稱。

    2. 從清單中選取「觸發條件類型」:

      • Google 來源:指定 Pub/Sub、Cloud Storage、Firestore 和其他 Google 事件提供者的觸發條件。

      • 第三方:與提供 Eventarc 來源的非 Google 供應商整合。詳情請參閱「Eventarc 中的第三方事件」。

    3. 從「Event provider」(事件提供者) 清單中選取「Cloud Firestore」,選擇提供觸發函式事件類型的產品。如需事件提供者清單,請參閱「事件提供者和目的地」。

    4. 從「事件類型」清單中選取「type=google.cloud.firestore.document.v1.written」。觸發條件設定會因支援的事件類型而異。詳情請參閱「事件類型」。

    5. 「Event data content type」(事件資料內容類型) 欄位維持不變。

    6. 在「Filters」(篩選器) 部分,選取資料庫、作業和屬性值,或使用預設選項。

    7. 如果「Region」(區域) 欄位已啟用,請選取 Eventarc 觸發條件的位置。一般來說,Eventarc 觸發條件的位置應與要監控事件的Google Cloud 資源位置相符。在多數情況下,您也應該在相同區域部署函式。如要進一步瞭解 Eventarc 觸發條件位置,請參閱「瞭解 Eventarc 位置」。

    8. 在「Service account」(服務帳戶) 欄位中,選取服務帳戶。Eventarc 觸發條件會連結至服務帳戶,在叫用函式時做為身分識別。Eventarc 觸發條件的服務帳戶必須具備叫用函式的權限。根據預設,Cloud Run 會使用 Compute Engine 預設服務帳戶。

    9. 您可以視需要指定要將傳入要求傳送至哪個「Service URL path」(服務網址路徑)。這是目的地服務上的相對路徑,觸發條件的事件應傳送至該路徑。例如:/、/route、route 和 route/subroute。

    10. 如要在傳送失敗時啟用重試功能,請勾選「Enable retry on failure」(啟用失敗時重試) 核取方塊。如未勾選,預設行為是只傳送一次,不重試。詳情請參閱「重試事件」。

  6. 填妥必填欄位後,按一下「儲存觸發條件」。

  7. 點選「建立」。

  8. 在「Source」(來源) 分頁中,視需要編輯原始碼,然後選取「Save and redeploy」(儲存並重新部署)。

gcloud

使用 gcloud CLI 建立函式時,必須先部署函式,再建立觸發條件。請按照下列步驟為函式建立觸發條件:

  1. 在包含程式碼範例的目錄中執行下列指令,部署函式:

    gcloud 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 區域。例如:europe-west1。

  2. 執行下列指令,建立可篩選及轉送事件的觸發條件:

    gcloud eventarc triggers create TRIGGER_NAME  \
        --location=LOCATION \
        --destination-run-service=FUNCTION  \
        --destination-run-region=DESTINATION_RUN_REGION \
        --event-filters="type=EVENT_FILTER_TYPE" \
        --event-filters=database='(default)' \
        --event-data-content-type=application/protobuf \
        --event-filters-path-pattern=document='users/{username}' \
        --service-account=SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com
    

    更改下列內容:

    • TRIGGER_NAME:觸發條件 ID 或完整 ID。
    • LOCATION:Eventarc 觸發條件的位置。或者,您也可以設定 eventarc/location 屬性,例如 gcloud config set eventarc/location us-central1。

      為避免效能和資料落地問題,位置必須與產生事件的 Google Cloud 服務位置相符。詳情請參閱「Eventarc 位置」。

    • FUNCTION:已部署的 Cloud Run 函式名稱,用於接收觸發條件的事件。
    • DESTINATION_RUN_REGION:(選用) 目的地 Cloud Run 函式所在的 Cloud Run 位置。如未指定,系統會假設函式與觸發條件位於相同區域。
    • EVENT_FILTER_TYPE:事件 ID。 如果方法 API 呼叫成功,系統就會產生事件。如果是長時間執行的作業,系統只會在作業結束時產生事件,且只有在動作成功執行時才會產生。如需支援的事件類型清單,請參閱「Eventarc 支援的 Google 事件類型」。
    • SERVICE_ACCOUNT_NAME:使用者自行管理的服務帳戶名稱。
    • PROJECT_ID:您的 Google Cloud 專案 ID。

    注意:

    • 觸發條件建立後,就無法變更事件篩選器類型。如要使用其他事件類型,則必須建立新的觸發條件。
    • --event-filters=type=google.cloud.firestore.document.v1.written 指定在建立、更新或刪除文件時,根據事件類型觸發函式。
    • --event-filters=database='(default)' 指定 Firebase 資料庫。預設資料庫名稱請使用 (default)。
    • --event-filters-path-pattern=document='users/{username}' 會提供應監控相關變更的文件路徑模式。這個路徑模式指出應監控 users 集合中的所有文件。詳情請參閱「瞭解路徑模式」。
    • 您也可以使用 --max-retry-attempts 旗標,指定只嘗試傳送事件一次,不重試。唯一有效的值是 1。如果省略旗標,系統會套用標準重試行為。詳情請參閱「重試事件」。
    • 另有其他旗標可供使用。詳情請參閱 gcloud eventarc triggers create。

Terraform

如要為 Cloud Run 函式建立 Eventarc 觸發條件,請參閱「使用 Terraform 建立觸發條件」。

測試 Hello Firestore 函式

如要測試 Hello Firestore 函式,請在 Firestore 資料庫中設定名為 users 的集合:

  1. 前往 Google Cloud 控制台的 Firestore「Databases」(資料庫) 頁面:

    前往 Firestore

  2. 按一下「建立珍藏內容」。

  3. 指定 users 做為集合 ID。

  4. 如要開始新增集合的第一份文件,請在「Add its first document」(新增第一份文件) 下方接受自動生成「Document ID」(文件 ID)。

  5. 請為文件新增至少一個欄位,並指定名稱和值。舉例來說,在「Field name」(欄位名稱) 中輸入 username,在「Field value」(欄位值) 中輸入 rowan。

  6. 完成時,請按一下 [Save] (儲存)。

    這項操作會建立新文件,進而觸發函式。

  7. 如要確認函式是否已觸發,請在 Google Cloud 控制台的「Cloud Run Overview」(Cloud Run 總覽) 頁面中,按一下函式的連結名稱,開啟「Service details」(服務詳細資料) 頁面。

  8. 在「Observability」(觀測能力) 分頁中,選取「Logs」(記錄) 分頁,然後尋找下列字串:

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

範例 2:轉換為大寫函式

以下範例會擷取使用者新增的值、將該位置的字串轉換為大寫,並以大寫字串取代該值:

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

部署「轉換為大寫」函式

如果您尚未設定 Firestore 資料庫,請先完成設定。

部署函式後,您可以使用 Google Cloud 控制台、Google Cloud CLI 或 Terraform 設定觸發條件。

控制台

使用 Google Cloud 控制台建立函式時,也可以為函式新增觸發條件。請按照下列步驟為函式建立觸發條件:

  1. 前往 Google Cloud 控制台的「Cloud Run」頁面:

    前往 Cloud Run。

  2. 按一下「編寫函式」,然後輸入函式詳細資料。如要進一步瞭解如何在部署期間設定函式,請參閱「部署函式」。

  3. 在「觸發條件」部分,按一下「新增觸發條件」。

  4. 選取「Firestore trigger」(Firestore 觸發條件)。

  5. 在「Eventarc trigger」(Eventarc 觸發條件) 窗格中,按照下列方式修改觸發條件詳細資料:

    1. 在「Trigger name」(觸發條件名稱) 欄位中輸入觸發條件名稱,或使用預設名稱。

    2. 從清單中選取「觸發條件類型」:

      • Google 來源:指定 Pub/Sub、Cloud Storage、Firestore 和其他 Google 事件提供者的觸發條件。

      • 第三方:與提供 Eventarc 來源的非 Google 供應商整合。詳情請參閱「Eventarc 中的第三方事件」。

    3. 從「Event provider」(事件提供者) 清單中選取「Firestore」,選擇提供函式觸發事件類型的產品。如需事件提供者清單,請參閱「事件提供者和目的地」。

    4. 從「事件類型」清單中選取「type=google.cloud.firestore.document.v1.written」。觸發條件設定會因支援的事件類型而異。詳情請參閱「事件類型」。

    5. 「Event data content type」(事件資料內容類型) 欄位維持不變。

    6. 在「篩選器」部分,選取資料庫、作業和屬性值,或使用預設選項。如果您已為資料庫命名,請在「Attribute value 1」(屬性值 1) 欄位中輸入名稱。

    7. 如果「Region」(區域) 欄位已啟用,請選取 Eventarc 觸發條件的位置。一般來說,Eventarc 觸發條件的位置應與要監控事件的Google Cloud 資源位置相符。在多數情況下,您也應該在相同區域部署函式。如要進一步瞭解 Eventarc 觸發條件位置,請參閱「瞭解 Eventarc 位置」。

    8. 在「Service account」(服務帳戶) 欄位中,選取服務帳戶。Eventarc 觸發條件會連結至服務帳戶,在叫用函式時做為身分識別。Eventarc 觸發條件的服務帳戶必須具備叫用函式的權限。根據預設,Cloud Run 會使用 Compute Engine 預設服務帳戶。

    9. 您可以視需要指定要將傳入要求傳送至哪個「Service URL path」(服務網址路徑)。這是目的地服務上的相對路徑,觸發條件的事件應傳送至該路徑。例如:/、/route、route 和 route/subroute。

    10. 如要在傳送失敗時啟用重試功能,請勾選「Enable retry on failure」(啟用失敗時重試) 核取方塊。如未勾選,預設行為是只傳送一次,不重試。詳情請參閱「重試事件」。

  6. 填妥必填欄位後,按一下「儲存觸發條件」。

  7. 點選「建立」。

  8. 在「Source」(來源) 分頁中,視需要編輯原始碼,然後選取「Save and redeploy」(儲存並重新部署)。

gcloud

使用 gcloud CLI 建立函式時,必須先部署函式,再建立觸發條件。請按照下列步驟為函式建立觸發條件:

  1. 在包含程式碼範例的目錄中執行下列指令,部署函式:

    gcloud 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 區域。例如:europe-west1。

  2. 執行下列指令,建立可篩選及轉送事件的觸發條件:

    gcloud eventarc triggers create TRIGGER_NAME  \
        --location=LOCATION \
        --destination-run-service=FUNCTION  \
        --destination-run-region=DESTINATION_RUN_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=SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com
    

    更改下列內容:

    • TRIGGER_NAME:觸發條件 ID 或完整 ID。
    • LOCATION:Eventarc 觸發條件的位置。或者,您也可以設定 eventarc/location 屬性,例如 gcloud config set eventarc/location us-central1。

      為避免任何效能和資料落地問題,位置必須與產生事件的 Google Cloud 服務位置相符。詳情請參閱「Eventarc 位置」。

    • FUNCTION:已部署的 Cloud Run 函式名稱,用於接收觸發條件的事件。
    • DESTINATION_RUN_REGION:(選用) 目的地 Cloud Run 函式所在的 Cloud Run 位置。如未指定,系統會假設函式與觸發條件位於相同區域。
    • EVENT_FILTER_TYPE:事件 ID。 如果方法 API 呼叫成功,系統就會產生事件。如果是長時間執行的作業,系統只會在作業結束時產生事件,且只有在動作成功執行時才會產生。如需支援的事件類型清單,請參閱「Eventarc 支援的 Google 事件類型」。
    • SERVICE_ACCOUNT_NAME:使用者自行管理的服務帳戶名稱。
    • PROJECT_ID:您的 Google Cloud 專案 ID。

    注意:

    • 觸發條件建立後,就無法變更事件篩選器類型。如要使用其他事件類型,則必須建立新的觸發條件。
    • --event-filters=type=google.cloud.firestore.document.v1.written 指定在建立、更新或刪除文件時,根據事件類型觸發函式。
    • --event-filters=database='(default)' 指定 Firebase 資料庫。預設資料庫名稱請使用 (default)。
    • --event-filters-path-pattern=document='users/{username}' 提供應監控相關變更的文件路徑模式。這個路徑模式指出應監控 users 集合中的所有文件。詳情請參閱「瞭解路徑模式」。
    • 您也可以使用 --max-retry-attempts 旗標,指定只嘗試傳送事件一次,不重試。唯一有效的值是 1。如果省略旗標,系統會套用標準重試行為。詳情請參閱「重試事件」。
    • 另有其他旗標可供使用。詳情請參閱 gcloud eventarc triggers create。

Terraform

如要為 Cloud Run 函式建立 Eventarc 觸發條件,請參閱「使用 Terraform 建立觸發條件」。

其他欄位則維持原樣:

  • --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」函式,請在 Firestore 資料庫中設定名為 messages 的集合:

  1. 前往 Google Cloud 控制台的 Firestore「Databases」(資料庫) 頁面:

    前往 Firestore

  2. 選取 Firestore 資料庫的資料庫 ID。

  3. 按一下「建立珍藏內容」。

  4. 指定 messages 做為集合 ID。

  5. 如要開始新增集合的第一份文件,請在「Add its first document」(新增第一份文件) 下方接受自動生成「Document ID」(文件 ID)。

  6. 如要觸發已部署的函式,請新增文件,並將「Field name」(欄位名稱) 設為 original,「Field value」(欄位值) 設為 minka。

  7. 儲存文件時,值欄位中的小寫字詞會轉換為大寫。

    如果您隨後編輯欄位值,使其包含小寫字母,系統會再次觸發函式,將所有小寫字母轉換為大寫。

函式限制

  • 函式的叫用並無一定順序。在快速變更的情形下,可能會以非預期的順序觸發函式叫用。
  • 事件至少會傳送一次,但單一事件可能會導致多次函式叫用。請避免依賴「恰好一次」的機制,並務必編寫冪等函式。
  • 每項觸發條件僅能關聯至單一資料庫,無法建立同時比對多個資料庫的觸發條件。
  • 刪除資料庫時,相關觸發條件並不自動隨之刪除,觸發條件會停止傳送事件,但會持續存在,直到您刪除觸發條件為止。