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
}

자바

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 리소스 위치와 일치해야 합니다. 대부분의 시나리오에서는 함수도 동일한 리전에 배포해야 합니다. Eventarc 트리거 위치에 대한 자세한 내용은 Eventarc 위치 이해를 참조하세요.

    7. 서비스 계정 필드에서 서비스 계정을 선택합니다. Eventarc 트리거는 함수를 호출할 때 ID로 사용할 서비스 계정에 연결됩니다. Eventarc 트리거의 서비스 계정은 함수 호출 권한이 있어야 합니다. 기본적으로 Cloud Run은 Compute Engine 기본 서비스 계정을 사용합니다.

    8. 선택적으로 수신 요청을 전송할 서비스 URL 경로를 지정합니다. 이는 트리거의 이벤트가 전송되어야 하는 대상 서비스의 상대 경로입니다. 예를 들면 /, /route, route, route/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 필터와 일치하는 이벤트가 함수 호출을 트리거합니다. 각 트리거는 지원되는 이벤트 유형이어야 합니다. 생성 후에는 이벤트 필터 유형을 변경할 수 없습니다. 이벤트 필터 유형을 변경하려면 새 트리거를 만들고 이전 트리거를 삭제해야 합니다. 원하는 경우 ATTRIBUTE=VALUE 형식으로 지원되는 필터를 사용해서 --event-filters 플래그를 반복하여 더 많은 필터를 추가할 수 있습니다.

다른 필드는 있는 다음과 같이 사용합니다.

  • --event-filters=type=google.cloud.firestore.document.v1.writtengoogle.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. users를 컬렉션 ID로 지정합니다.

  4. 컬렉션의 첫 번째 문서 추가를 시작하려면 첫 번째 문서 추가에서 자동 생성된 문서 ID를 수락합니다.

  5. 문서에 대한 필드를 하나 이상 추가하고 이름과 값을 지정합니다. 예를 들어 필드 이름username을 입력하고 필드 값rowan을 입력합니다.

  6. 완료했으면 저장을 클릭합니다.

    이 작업은 새 문서를 만들어 함수를 트리거합니다.

  7. 함수가 트리거되었는지 확인하려면 Google Cloud 콘솔 Cloud Run 개요 페이지에서 함수의 링크된 이름을 클릭하여 서비스 세부정보 페이지를 엽니다.

  8. 로그 탭을 선택하고 다음 문자열을 찾습니다.

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
}

자바

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 콘솔을 사용하여 함수를 만들 때 함수에 트리거를 추가할 수도 있습니다. 함수의 트리거를 만들려면 다음 단계를 따르세요.

  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 리소스 위치와 일치해야 합니다. 대부분의 시나리오에서는 함수도 동일한 리전에 배포해야 합니다. Eventarc 트리거 위치에 대한 자세한 내용은 Eventarc 위치 이해를 참조하세요.

    7. 서비스 계정 필드에서 서비스 계정을 선택합니다. Eventarc 트리거는 함수를 호출할 때 ID로 사용할 서비스 계정에 연결됩니다. Eventarc 트리거의 서비스 계정은 함수 호출 권한이 있어야 합니다. 기본적으로 Cloud Run은 Compute Engine 기본 서비스 계정을 사용합니다.

    8. 선택적으로 수신 요청을 전송할 서비스 URL 경로를 지정합니다. 이는 트리거의 이벤트가 전송되어야 하는 대상 서비스의 상대 경로입니다. 예를 들면 /, /route, route, route/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 필터와 일치하는 이벤트가 함수 호출을 트리거합니다. 각 트리거는 지원되는 이벤트 유형이어야 합니다. 생성 후에는 이벤트 필터 유형을 변경할 수 없습니다. 이벤트 필터 유형을 변경하려면 새 트리거를 만들고 이전 트리거를 삭제해야 합니다. 원하는 경우 ATTRIBUTE=VALUE 형식으로 지원되는 필터를 사용해서 --event-filters 플래그를 반복하여 더 많은 필터를 추가할 수 있습니다.

다른 필드는 있는 다음과 같이 사용합니다.

  • --event-filters=type=google.cloud.firestore.document.v1.writtengoogle.cloud.firestore.document.v1.written 이벤트 유형에 따라 문서가 생성, 업데이트 또는 삭제될 때 함수가 트리거되도록 지정합니다.
  • --event-filters=database='(default)'는 Firestore 데이터베이스를 지정합니다. 기본 데이터베이스 이름에는 (default)를 사용합니다.
  • --event-filters-path-pattern=document='messages/{pushId}'은 관련 변경사항을 모니터링해야 하는 문서의 경로 패턴을 제공합니다. 이 경로 패턴은 messages 컬렉션의 모든 문서를 모니터링해야 함을 나타냅니다. 자세한 내용은 경로 패턴 이해를 참조하세요.

대문자로 변환 함수 테스트

방금 배포한 대문자로 변환 함수를 테스트하려면 Firestore 데이터베이스에서 messages라는 컬렉션을 설정합니다.

  1. Google Cloud 콘솔에서 Firestore 데이터베이스 페이지로 이동합니다.

    Firestore로 이동

  2. 컬렉션 시작을 클릭합니다.

  3. messages를 컬렉션 ID로 지정합니다.

  4. 컬렉션의 첫 번째 문서 추가를 시작하려면 첫 번째 문서 추가에서 자동 생성된 문서 ID를 수락합니다.

  5. 배포된 함수를 트리거하려면 필드 이름original이고 필드 값minka인 문서를 추가합니다.

  6. 문서를 저장하면 값 필드의 소문자가 대문자로 변환됩니다.

    나중에 소문자를 포함하도록 필드 값을 수정하면 함수가 다시 트리거되어 모든 소문자가 대문자로 변환됩니다.

함수 제한사항

  • 순서는 보장되지 않습니다. 급격하게 변경하면 예기치 않은 순서로 함수 호출이 트리거될 수 있습니다.
  • 이벤트는 최소 1회 전송되지만 하나의 이벤트에 함수가 여러 번 호출될 수 있습니다. 정확히 한 번에 처리하는 메커니즘에 의존하지 말고 멱등 함수를 작성하세요.
  • 트리거는 단일 데이터베이스와 연결됩니다. 여러 데이터베이스와 일치하는 트리거를 만들 수 없습니다.
  • 데이터베이스를 삭제해도 해당 데이터베이스의 트리거가 자동으로 삭제되지 않습니다. 트리거가 이벤트 제공을 중지하지만 트리거를 삭제하기 전까지 계속 존재합니다.