Memperluas Cloud Run dengan pemicu peristiwa menggunakan fungsi Cloud Run

Dengan fungsi Cloud Run, Anda dapat men-deploy kode untuk menangani peristiwa yang dipicu oleh perubahan pada database Cloud Run. Dengan begitu, Anda dapat menambahkan fungsionalitas sisi server tanpa harus menjalankan server Anda sendiri.

Panduan ini menjelaskan cara membuat pemicu untuk fungsi Cloud Run dari peristiwa Firestore.

Anda dapat memicu fungsi Cloud Run dari peristiwa di database Firestore. Saat dipicu, fungsi Anda akan membaca dan mengupdate database Firestore sebagai respons terhadap peristiwa ini melalui API Firestore dan library klien.

Proses peristiwa Firestore yang memicu fungsi Cloud Run terdiri dari langkah-langkah berikut:

  1. Layanan menunggu perubahan pada dokumen tertentu.

  2. Saat perubahan terjadi, layanan akan dipicu dan menjalankan tugasnya.

  3. Layanan menerima objek data dengan snapshot dokumen yang terpengaruh. Untuk peristiwa write atau update, objek data berisi snapshot yang mewakili status dokumen sebelum dan setelah peristiwa pemicuan.

Sebelum memulai

  1. Pastikan Anda telah menyiapkan project baru untuk Cloud Run seperti yang dijelaskan di halaman setup.
  2. Aktifkan Artifact Registry, Cloud Build, Cloud Run Admin API, Eventarc, Firestore Cloud Logging, dan Pub/Sub API:

    Aktifkan API

Peran yang diperlukan

Anda atau administrator Anda harus memberikan akses ke akun deployer dan identitas pemicu. Secara opsional, berikan peran IAM berikut kepada agen layanan Pub/Sub.

Peran yang diperlukan untuk akun deployer

Untuk mendapatkan izin yang Anda perlukan untuk memicu dari peristiwa Firestore, minta administrator Anda untuk memberi Anda peran IAM berikut di project Anda:

Untuk mengetahui informasi selengkapnya tentang cara memberikan peran, lihat Mengelola akses ke project, folder, dan organisasi.

Anda mungkin juga bisa mendapatkan izin yang diperlukan melalui peran khusus atau peran bawaan lainnya.

Perhatikan bahwa secara default, izin Cloud Build mencakup izin untuk mengupload dan mendownload artefak Artifact Registry.

Peran yang diperlukan untuk identitas pemicu

  1. Catat akun layanan default Compute Engine karena Anda akan melampirkannya ke pemicu Eventarc untuk merepresentasikan identitas pemicu untuk tujuan pengujian. Akun layanan ini dibuat secara otomatis setelah mengaktifkan atau menggunakan layanan Google Cloud yang menggunakan Compute Engine, dan dengan format email berikut:

    PROJECT_NUMBER-compute@developer.gserviceaccount.com

    Ganti PROJECT_NUMBER dengan Google Cloud nomor project Anda. Anda dapat menemukan nomor project di halaman Selamat Datang di konsol Google Cloud atau dengan menjalankan perintah berikut:

    gcloud projects describe PROJECT_ID --format='value(projectNumber)'

    Untuk lingkungan produksi, sebaiknya buat akun layanan baru dan berikan satu atau beberapa peran IAM yang berisi izin minimum yang diperlukan dan ikuti prinsip hak istimewa terendah.

  2. Secara default, layanan Cloud Run hanya dapat dipanggil oleh Project Owner, Project Editor, Cloud Run Admin dan Invoker. Anda dapat mengontrol akses per layanan; namun, untuk tujuan pengujian, berikan peran Cloud Run Invoker (run.invoker) di project Google Cloud ke akun layanan Compute Engine. Peran ini akan memberikan peran tersebut di semua layanan dan tugas Cloud Run dalam suatu project.
    gcloud projects add-iam-policy-binding PROJECT_ID \
        --member=serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com \
        --role=roles/run.invoker

    Perhatikan bahwa pemicu akan berhasil dibuat dan diaktifkan jika Anda membuat pemicu untuk layanan Cloud Run yang diautentikasi tanpa memberikan peran Cloud Run Namun, pemicu tidak akan berfungsi seperti yang diharapkan dan pesan yang mirip dengan berikut ini akan muncul di log:

    The request was not authenticated. Either allow unauthenticated invocations or set the proper Authorization header.
  3. Berikan Peran Eventarc Event Receiver (roles/eventarc.eventReceiver) pada project ke akun layanan default Compute Engine agar pemicu Eventarc dapat menerima peristiwa dari penyedia peristiwa.
    gcloud projects add-iam-policy-binding PROJECT_ID \
        --member=serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com \
        --role=roles/eventarc.eventReceiver

Peran opsional untuk agen layanan Pub/Sub

  • Jika Anda mengaktifkan agen layanan Cloud Pub/Sub pada atau sebelum 8 April 2021, untuk mendukung permintaan push Pub/Sub yang diautentikasi, berikan peran Service Account Token Creator (roles/iam.serviceAccountTokenCreator) kepada agen layanan. Jika tidak, peran ini akan diberikan secara default:
    gcloud projects add-iam-policy-binding PROJECT_ID \
        --member=serviceAccount:service-PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com \
        --role=roles/iam.serviceAccountTokenCreator

Menyiapkan database Firestore

Sebelum men-deploy layanan, Anda harus membuat database Firestore:

  1. Buka halaman Firestore.

  2. Pilih Create a Firestore database.

  3. Di kolom Name your database, masukkan ID Database, seperti firestore-db.

  4. Di bagian Opsi konfigurasi, Firestore native dipilih secara default bersama dengan aturan keamanan yang berlaku.

  5. Di Location type, pilih Region dan pilih region tempat database Anda berada. Pilihan ini bersifat permanen.

  6. Klik Buat database.

Model data Firestore terdiri dari koleksi yang berisi dokumen. Setiap dokumen berisi kumpulan key-value pair.

Menulis fungsi yang dipicu Firestore

Untuk menulis fungsi yang merespons peristiwa Firestore, bersiaplah untuk menentukan hal berikut selama deployment:

Jenis peristiwa

Firestore mendukung peristiwa create, update, delete, dan write. Peristiwa write mencakup semua perubahan pada dokumen.

Jenis peristiwa Pemicu
google.cloud.firestore.document.v1.created (default) Dipicu saat dokumen ditulisi untuk pertama kalinya.
google.cloud.firestore.document.v1.updated Dipicu saat dokumen sudah ada dan nilainya berubah.
google.cloud.firestore.document.v1.deleted Dipicu saat dokumen yang memuat data dihapus.
google.cloud.firestore.document.v1.written Dipicu saat dokumen dibuat, diperbarui, atau dihapus.
google.cloud.firestore.document.v1.created.withAuthContext Sama seperti created, tetapi menambahkan informasi autentikasi.
google.cloud.firestore.document.v1.updated.withAuthContext Sama seperti updated, tetapi menambahkan informasi autentikasi.
google.cloud.firestore.document.v1.deleted.withAuthContext Sama seperti deleted, tetapi menambahkan informasi autentikasi.
google.cloud.firestore.document.v1.written.withAuthContext Sama seperti written, tetapi menambahkan informasi autentikasi.

Karakter pengganti ditulis dalam pemicu menggunakan tanda kurung kurawal, misalnya: projects/YOUR_PROJECT_ID/databases/(default)/documents/collection/{document_wildcard}

Filter peristiwa pemicu

Untuk memicu layanan Anda, tentukan jalur dokumen yang akan diproses. Jalur dokumen harus berada dalam project yang sama dengan layanan. Google Cloud

Berikut adalah beberapa contoh jalur dokumen yang valid:

  • users/marie: Memantau satu dokumen, /users/marie.

  • users/{username}: Memantau semua dokumen pengguna. Karakter pengganti digunakan untuk memantau semua dokumen dalam koleksi.

  • users/{username}/addresses/home: Memantau dokumen alamat rumah untuk semua pengguna.

  • users/{username}/addresses/{addressId}: Memantau semua dokumen alamat.

  • users/{user=**}: Memantau semua dokumen pengguna dan setiap dokumen dalam subkoleksi pada setiap dokumen pengguna seperti /users/userID/address/home atau /users/userID/phone/work.

  • users/{username}/addresses: jalur alamat tidak valid. Mengacu pada subkoleksi addresses, bukan dokumen.

Karakter pengganti dan parameter

Jika tidak mengetahui secara spesifik dokumen yang ingin dipantau, gunakan {wildcard}, bukan ID dokumen:

  • users/{username} akan memproses perubahan pada semua dokumen pengguna.

Dalam contoh ini, saat kolom dalam dokumen pada users diubah, maka akan dicocokkan dengan karakter pengganti yang disebut {username}.

Jika dokumen dalam users memiliki subkoleksi, dan kolom di salah satu dokumen subkoleksi tersebut diubah, karakter pengganti {username} tidak akan terpicu. Jika sasaran Anda adalah juga merespons peristiwa di subkoleksi, gunakan karakter pengganti multi-segmen {username=**}.

Kecocokan karakter pengganti diekstrak dari jalur dokumen. Anda dapat menentukan karakter pengganti sebanyak yang Anda inginkan untuk mengganti koleksi eksplisit atau ID dokumen. Anda dapat menggunakan maksimal satu karakter pengganti multi-segmen seperti {username=**}.

Kode fungsi

Lihat contoh cara menggunakan peristiwa Firestore dalam Mode Native untuk memicu fungsi Cloud Run.

Sertakan dependensi proto dalam sumber Anda

Anda harus menyertakan file Cloud Run data.proto di direktori sumber untuk fungsi Anda. File ini mengimpor proto berikut yang juga harus Anda sertakan dalam direktori sumber:

Gunakan struktur direktori yang sama untuk dependensi. Misalnya, tempatkan struct.proto dalam google/protobuf.

File ini diperlukan untuk mendekode data peristiwa. Jika sumber fungsi Anda tidak menyertakan file ini, fungsi akan menampilkan error saat dijalankan.

Atribut peristiwa

Setiap peristiwa mencakup atribut data yang mencakup informasi tentang peristiwa seperti waktu pemicuan peristiwa. Cloud Run menambahkan data tambahan tentang database dan dokumen yang terlibat dalam peristiwa. Anda dapat mengakses atribut ini sebagai berikut:

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']}")

Struktur peristiwa

Pemicu ini memanggil layanan Anda dengan peristiwa yang mirip dengan:

{
    "oldValue": { // Update and Delete operations only
        A Document object containing a pre-operation document snapshot
    },
    "updateMask": { // Update operations only
        A DocumentMask object that lists changed fields.
    },
    "value": {
        // A Document object containing a post-operation document snapshot
    }
}

Setiap objek Document berisi satu atau beberapa objek Value. Lihat dokumentasi Value untuk referensi jenis.

Membuat pemicu untuk fungsi

Klik tab untuk mendapatkan petunjuk cara menggunakan alat pilihan Anda.

Konsol

Saat menggunakan konsol Google Cloud untuk membuat fungsi, Anda juga dapat menambahkan pemicu ke fungsi. Ikuti langkah-langkah berikut untuk membuat pemicu bagi fungsi Anda:

  1. Di konsol Google Cloud , buka Cloud Run:

    Buka Cloud Run

  2. Klik Write a function, lalu masukkan detail fungsi. Untuk mengetahui informasi selengkapnya tentang cara mengonfigurasi fungsi selama deployment, lihat Men-deploy fungsi.

  3. Di bagian Pemicu, klik Tambahkan pemicu.

  4. Pilih Pemicu Firestore.

  5. Di panel Eventarc trigger, ubah detail pemicu sebagai berikut:

    1. Masukkan nama pemicu di kolom Nama pemicu, atau gunakan nama default.

    2. Pilih Jenis pemicu dari daftar:

      • Sumber Google untuk menentukan pemicu bagi Pub/Sub, Cloud Storage, Firestore, dan penyedia peristiwa Google lainnya.

      • Pihak ketiga untuk berintegrasi dengan penyedia non-Google yang menawarkan sumber Eventarc. Untuk mengetahui informasi selengkapnya, lihat Peristiwa pihak ketiga di Eventarc.

    3. Pilih Firestore dari daftar Event provider, untuk memilih produk yang menyediakan jenis peristiwa untuk memicu fungsi Anda. Untuk daftar penyedia peristiwa, lihat Penyedia dan tujuan peristiwa.

    4. Pilih type=google.cloud.firestore.document.v1.created dari daftar Event type. Konfigurasi pemicu Anda bervariasi, bergantung pada jenis peristiwa yang didukung. Untuk mengetahui informasi selengkapnya, lihat Jenis peristiwa.

    5. Di bagian Filter, pilih database, operasi, dan nilai atribut, atau gunakan pilihan default.

    6. Jika kolom Region diaktifkan, pilih lokasi untuk pemicu Eventarc. Secara umum, lokasi pemicu Eventarc harus cocok dengan lokasi resource Google Cloud yang ingin Anda pantau peristiwanya. Dalam sebagian besar skenario, Anda juga harus men-deploy fungsi di region yang sama. Lihat Memahami lokasi Eventarc untuk mengetahui detail selengkapnya tentang lokasi pemicu Eventarc.

    7. Di kolom Service account, pilih akun layanan. Pemicu Eventarc ditautkan ke akun layanan untuk digunakan sebagai identitas saat memanggil fungsi Anda. Akun layanan pemicu Eventarc Anda harus memiliki izin untuk memanggil fungsi Anda. Secara default, Cloud Run menggunakan akun layanan default Compute Engine.

    8. Jika perlu, tentukan Jalur URL layanan untuk mengirim permintaan masuk ke. Ini adalah jalur relatif pada layanan tujuan yang akan menjadi tempat pengiriman peristiwa untuk pemicu. Misalnya: /, /route, route, dan route/subroute.

  6. Setelah Anda mengisi kolom yang wajib diisi, klik Simpan pemicu.

gcloud

Saat membuat fungsi menggunakan gcloud CLI, Anda harus men-deploy fungsi terlebih dahulu, lalu membuat pemicu. Ikuti langkah-langkah berikut untuk membuat pemicu bagi fungsi Anda:

  1. Jalankan perintah berikut di direktori yang berisi kode contoh untuk men-deploy fungsi Anda:

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

    Ganti:

    • FUNCTION dengan nama fungsi yang Anda deploy. Anda dapat menghilangkan parameter ini sepenuhnya, tetapi Anda akan diminta untuk memasukkan nama jika menghilangkannya.

    • FUNCTION_ENTRYPOINT dengan titik entri ke fungsi Anda dalam kode sumber. Ini adalah kode yang dieksekusi Cloud Run saat fungsi Anda berjalan. Nilai flag ini harus berupa nama fungsi atau nama class yang sepenuhnya memenuhi syarat yang ada dalam kode sumber Anda.

    • BASE_IMAGE_ID dengan lingkungan image dasar untuk fungsi Anda. Untuk mengetahui detail selengkapnya tentang image dasar dan paket yang disertakan dalam setiap image, lihat Image dasar runtime.

    • REGION dengan Google Cloud region tempat Anda ingin men-deploy fungsi Anda. Contohnya, europe-west1

  2. Jalankan perintah berikut untuk membuat pemicu yang memfilter peristiwa:

    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.created" \
        --service-account=PROJECT_NUMBER-compute@developer.gserviceaccount.com
    

    Ganti:

    • TRIGGER_NAME dengan nama pemicu Anda.

    • EVENTARC_TRIGGER_LOCATION dengan lokasi untuk pemicu Eventarc. Secara umum, lokasi pemicu Eventarc harus cocok dengan lokasi resource Google Cloud yang ingin Anda pantau peristiwanya. Dalam sebagian besar skenario, Anda juga harus men-deploy fungsi di region yang sama. Untuk mengetahui informasi selengkapnya, lihat Lokasi Eventarc.

    • FUNCTION dengan nama fungsi yang Anda deploy.

    • REGION dengan region Cloud Run fungsi.

    • PROJECT_NUMBER dengan Google Cloud nomor project Anda. Pemicu Eventarc ditautkan ke akun layanan untuk digunakan sebagai identitas saat memanggil fungsi Anda. Akun layanan pemicu Eventarc Anda harus memiliki izin untuk memanggil fungsi Anda. Secara default, Cloud Run menggunakan Akun layanan komputasi default.

    Setiap tanda event-filters menentukan jenis peristiwa, dengan fungsi dipicu hanya jika peristiwa memenuhi semua kriteria yang ditentukan dalam tanda event-filters-nya. Setiap pemicu harus memiliki tanda event-filters yang menentukan jenis peristiwa yang didukung, seperti dokumen baru yang ditulis ke Firestore atau file yang diupload ke Cloud Storage. Anda tidak dapat mengubah jenis filter peristiwa setelah pembuatan. Untuk mengubah jenis filter peristiwa, Anda harus membuat pemicu baru dan menghapus pemicu lama. Secara opsional, Anda dapat mengulangi tanda --event-filters dengan filter yang didukung dalam bentuk ATTRIBUTE=VALUE untuk menambahkan lebih banyak filter.

Terraform

Untuk membuat pemicu Eventarc bagi fungsi Cloud Run, lihat Membuat pemicu menggunakan Terraform.

Contoh

Contoh berikut menjelaskan cara menggunakan peristiwa Firestore dalam Mode Native untuk memicu fungsi Cloud Run.

Contoh 1: Fungsi Hello Firestore

Contoh berikut mencetak kolom peristiwa Firestore yang memicu:

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

Men-deploy fungsi

Untuk men-deploy fungsi Hello Firestore, jalankan perintah berikut:

Jika Anda belum melakukannya, siapkan database Firestore.

Untuk men-deploy fungsi, lihat Membuat pemicu untuk fungsi.

Menguji fungsi

Untuk menguji fungsi Hello Firestore, siapkan koleksi bernama users di database Firestore Anda:

  1. Di konsol Google Cloud , buka halaman database Firestore:

    Buka Firestore

  2. Klik Mulai koleksi.

  3. Tentukan users sebagai ID koleksi.

  4. Untuk mulai menambahkan dokumen pertama koleksi, di bagian Tambahkan dokumen pertamanya, terima ID Dokumen yang dihasilkan secara otomatis.

  5. Tambahkan setidaknya satu kolom untuk dokumen, dengan menentukan nama dan nilai. Misalnya, di Nama kolom, masukkan username, dan di Nilai kolom, masukkan rowan.

  6. Bila telah selesai, klik Simpan.

    Tindakan ini akan membuat dokumen baru, sehingga memicu fungsi Anda.

  7. Untuk mengonfirmasi bahwa fungsi Anda dipicu, klik nama fungsi tertaut di Google Cloud konsol halaman Ringkasan Cloud Run untuk membuka halaman Detail layanan.

  8. Pilih tab Log, lalu cari string ini:

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

Contoh 2: Fungsi Mengonversi ke Huruf Besar

Contoh berikut mengambil nilai yang ditambahkan oleh pengguna, mengubah string di lokasi tersebut menjadi huruf besar, dan mengganti nilai dengan string huruf besar:

Node.js

Gunakan protobufjs untuk mendekode data peristiwa. Sertakan google.events.cloud.firestore.v1 data.proto dalam sumber Anda.

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

Men-deploy fungsi

Untuk men-deploy fungsi Convert to Uppercase, jalankan perintah berikut:

Jika Anda belum melakukannya, siapkan database Firestore.

Untuk men-deploy fungsi, lihat Membuat pemicu untuk fungsi.

Menguji fungsi

Untuk menguji fungsi Convert to Uppercase yang baru saja Anda deploy, siapkan koleksi bernama messages di database Firestore Anda:

  1. Di konsol Google Cloud , buka halaman database Firestore:

    Buka Firestore

  2. Klik Mulai koleksi.

  3. Tentukan messages sebagai ID koleksi.

  4. Untuk mulai menambahkan dokumen pertama koleksi, di bagian Tambahkan dokumen pertamanya, terima ID Dokumen yang dihasilkan secara otomatis.

  5. Untuk memicu fungsi yang di-deploy, tambahkan dokumen dengan Nama kolom original dan Nilai kolom minka.

  6. Saat menyimpan dokumen, Anda dapat melihat kata dalam huruf kecil di kolom nilai yang dikonversi menjadi huruf besar.

    Jika kemudian Anda mengedit nilai kolom agar berisi huruf kecil, hal tersebut akan memicu fungsi lagi, dan mengonversi semua huruf kecil menjadi huruf besar.

Batasan untuk fungsi

  • Pengurutan tidak dijamin. Perubahan cepat dapat memicu pemanggilan fungsi dalam urutan yang tidak terduga.
  • Peristiwa dikirim setidaknya satu kali, tetapi satu peristiwa dapat menghasilkan beberapa pemanggilan fungsi. Hindari mengandalkan mekanisme tepat satu kali, dan tulis fungsi idempoten.
  • Pemicu dikaitkan dengan satu database. Anda tidak dapat membuat pemicu yang cocok dengan beberapa database.
  • Menghapus database tidak secara otomatis menghapus pemicu untuk database tersebut. Pemicu berhenti mengirim peristiwa, tetapi akan tetap ada sampai Anda menghapus pemicu.