Étendre Cloud Run avec des déclencheurs d'événements à l'aide de Cloud Run Functions

Avec Cloud Run Functions, vous pouvez déployer du code pour gérer les événements déclenchés lorsque votre base de données Cloud Run est modifiée. Ce service vous permet d'ajouter des fonctionnalités côté serveur sans avoir à gérer vos propres serveurs.

Ce guide explique comment créer des déclencheurs pour les fonctions Cloud Run à partir d'événements Firestore.

Vous pouvez déclencher vos fonctions Cloud Run à partir d'événements dans une base de données Firestore. Une fois déclenchée, votre fonction lit et met à jour une base de données Firestore en réponse à ces événements via les API et bibliothèques clientes Firestore.

Le processus de déclenchement d'une fonction Cloud Run par des événements Firestore comprend les étapes suivantes :

  1. Le service attend que des modifications soient apportées à un document donné.

  2. Lorsqu'un changement se produit, le service est déclenché et exécute ses tâches.

  3. Le service reçoit un objet de données contenant un instantané du document affecté. Pour les événements write ou update, l'objet de données contient des instantanés représentant l'état du document avant et après l'événement déclencheur.

Avant de commencer

  1. Assurez-vous d'avoir configuré un nouveau projet pour Cloud Run, comme décrit sur la page de configuration.
  2. Activez les API Artifact Registry, Cloud Build, Cloud Run Admin, Eventarc, Firestore Cloud Logging et Pub/Sub :

    Activer les API

Rôles requis

Vous ou votre administrateur devez accorder l'identité du compte de déploiement et du déclencheur. Vous pouvez également attribuer les rôles IAM suivants à l'agent de service Pub/Sub.

Rôles requis pour le compte déployeur

Pour obtenir les autorisations nécessaires pour déclencher des événements Firestore, demandez à votre administrateur de vous accorder les rôles IAM suivants sur votre projet :

Pour en savoir plus sur l'attribution de rôles, consultez la page Gérer l'accès aux projets, aux dossiers et aux organisations.

Vous pouvez également obtenir les autorisations requises avec des rôles personnalisés ou d'autres rôles prédéfinis.

Notez que par défaut, les autorisations Cloud Build incluent des autorisations permettant d'importer et de télécharger des artefacts Artifact Registry.

Rôles requis pour l'identité du déclencheur

  1. Notez le compte de service Compute Engine par défaut, car vous allez l'associer à un déclencheur Eventarc pour représenter l'identité du déclencheur à des fins de test. Ce compte de service est créé automatiquement après l'activation ou l'utilisation d'un service Google Cloud qui utilise Compute Engine, avec le format d'adresse e-mail suivant :

    PROJECT_NUMBER-compute@developer.gserviceaccount.com

    Remplacez PROJECT_NUMBER par le numéro de votre projet Google Cloud. Vous pouvez trouver le numéro de votre projet sur la page Bienvenue de la console Google Cloud ou en exécutant la commande suivante :

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

    Pour les environnements de production, nous vous recommandons vivement de créer un compte de service et de lui attribuer un ou plusieurs rôles IAM contenant les autorisations minimales requises conformément au principe du moindre privilège.

  2. Par défaut, les services Cloud Run ne peuvent être appelés que par les propriétaires de projet, les éditeurs de projet, ainsi que par les administrateurs et les demandeurs Cloud Run. Vous pouvez contrôler l'accès service par service. Toutefois, à des fins de test, attribuez le rôle Demandeur Cloud Run (run.invoker) au compte de service Compute Engine sur le projet Google Cloud . Cela permet d'accorder le rôle sur tous les services et jobs Cloud Run d'un projet.
    gcloud projects add-iam-policy-binding PROJECT_ID \
        --member=serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com \
        --role=roles/run.invoker

    Notez que si vous créez un déclencheur pour un service Cloud Run authentifié sans attribuer le rôle Demandeur Cloud Run, le déclencheur est bien créé et actif. Cependant, le déclencheur ne fonctionnera pas comme prévu et un message semblable au suivant s'affichera dans les journaux :

    The request was not authenticated. Either allow unauthenticated invocations or set the proper Authorization header.
  3. Attribuez le rôle Récepteur d'événements Eventarc (roles/eventarc.eventReceiver) sur le projet au compte de service Compute Engine par défaut afin que le déclencheur Eventarc puisse recevoir des événements en provenance des fournisseurs d'événements
    gcloud projects add-iam-policy-binding PROJECT_ID \
        --member=serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com \
        --role=roles/eventarc.eventReceiver

Rôle facultatif pour l'agent de service Pub/Sub

  • Si vous avez activé l'agent de service Cloud Pub/Sub le 8 avril 2021 ou à une date antérieure, attribuez le rôle Créateur de jetons du compte de service (roles/iam.serviceAccountTokenCreator) au compte de service pour accepter les requêtes push Pub/Sub authentifiées. Sinon, ce rôle est attribué par défaut :
    gcloud projects add-iam-policy-binding PROJECT_ID \
        --member=serviceAccount:service-PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com \
        --role=roles/iam.serviceAccountTokenCreator

Configurer votre base de données Cloud Firestore

Avant de déployer votre service, vous devez créer une base de données Firestore :

  1. Accédez à la page Firestore.

  2. Sélectionnez Créer une base de données Firestore.

  3. Dans le champ Nommez votre base de données, saisissez un ID de base de données, tel que firestore-db.

  4. Dans la section Options de configuration, l'option Firestore natif est sélectionnée par défaut, ainsi que les règles de sécurité applicables.

  5. Dans Type d'emplacement, sélectionnez Région, puis choisissez la région où doit être située votre base de données. Ce choix est définitif.

  6. Cliquez sur Créer une base de données.

Le modèle de données Firestore est constitué de collections contenant des documents. Chaque document contient un ensemble de paires clé/valeur.

Écrire une fonction déclenchée par Firestore

Pour écrire une fonction qui répond aux événements Firestore, préparez-vous à spécifier les éléments suivants lors du déploiement :

Types d'événement

Firestore accepte les événements create, update, delete et write. L'événement write englobe toutes les modifications apportées à un document.

Type d'événement Déclencheur
google.cloud.firestore.document.v1.created (par défaut) Déclenché lorsqu'un document est écrit pour la première fois.
google.cloud.firestore.document.v1.updated Déclenché lorsqu'un document existe déjà et qu'une valeur y a été modifiée.
google.cloud.firestore.document.v1.deleted Déclenché lorsqu'un document contenant des données est supprimé.
google.cloud.firestore.document.v1.written Déclenché lorsqu'un document est créé, mis à jour ou supprimé.
google.cloud.firestore.document.v1.created.withAuthContext Identique à created, mais ajoute des informations d'authentification.
google.cloud.firestore.document.v1.updated.withAuthContext Identique à updated, mais ajoute des informations d'authentification.
google.cloud.firestore.document.v1.deleted.withAuthContext Identique à deleted, mais ajoute des informations d'authentification.
google.cloud.firestore.document.v1.written.withAuthContext Identique à written, mais ajoute des informations d'authentification.

Les caractères génériques sont écrits dans les déclencheurs à l'aide d'accolades, par exemple : projects/YOUR_PROJECT_ID/databases/(default)/documents/collection/{document_wildcard}

Filtres des événements déclencheurs

Pour déclencher votre service, spécifiez un chemin d'accès de document à écouter. Le chemin d'accès aux documents doit se trouver dans le même projet Google Cloud que le service.

Voici quelques exemples de chemins d'accès aux documents valides :

  • users/marie : surveille un seul document, /users/marie.

  • users/{username} : surveille tous les documents utilisateur. Les caractères génériques permettent de surveiller tous les documents de la collection.

  • users/{username}/addresses/home : surveille le document d'adresses personnelles de tous les utilisateurs.

  • users/{username}/addresses/{addressId} : surveille tous les documents d'adresses.

  • users/{user=**} : surveille tous les documents d'utilisateur et tous les documents des sous-collections associées à chaque document d'utilisateur, telles que /users/userID/address/home ou /users/userID/phone/work.

  • users/{username}/addresses : chemin d'accès à l'adresse non valide. Renvoie à la sous-collection addresses, et non à un document.

Caractères génériques et paramètres

Si vous ne connaissez pas le nom du document spécifique que vous souhaitez surveiller, utilisez un caractère générique ({wildcard}) à la place de l'ID du document :

  • users/{username} : écoute les modifications apportées à tous les documents d'utilisateur.

Dans cet exemple, lorsqu'un champ de n'importe quel document de la collection users est modifié, il correspond au caractère générique {username}.

Si un document de la collection users comporte des sous-collections et qu'un champ de l'une d'entre elles est modifié, le caractère générique {username} n'est pas déclenché. Si votre objectif est de répondre aux événements dans les sous-collections, utilisez le caractère générique multisegment {username=**}.

Les correspondances de caractères génériques sont extraites du chemin du document. Vous pouvez définir autant de caractères génériques que vous le souhaitez pour remplacer les ID explicites de collection ou de document. Vous ne pouvez utiliser qu'un seul caractère générique multisegment, tel que {username=**}.

Code de la fonction

Consultez les exemples pour savoir comment utiliser les événements Firestore en mode natif pour déclencher une fonction Cloud Run.

Incluez les dépendances proto dans votre source.

Vous devez inclure le fichier data.proto Cloud Run dans le répertoire source de votre fonction. Ce fichier importe les protos suivants, que vous devez également inclure dans votre répertoire source :

Utilisez la même structure de répertoire pour les dépendances. Par exemple, placez struct.proto dans google/protobuf.

Ces fichiers sont nécessaires pour décoder les données d'événement. Si la source de votre fonction n'inclut pas ces fichiers, elle renvoie une erreur lors de son exécution.

Attributs d'événement

Chaque événement inclut des attributs de données qui contiennent des informations sur l'événement, comme l'heure à laquelle il s'est déclenché. Cloud Run ajoute des données supplémentaires sur la base de données et le document concernés par l'événement. Vous pouvez accéder à ces attributs comme suit :

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

Structures d'événements

Ce déclencheur appelle votre service avec un événement semblable à celui-ci :

{
    "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
    }
}

Chaque objet Document contient un ou plusieurs objets Value. Consultez la documentation de référence de l'objet Value pour obtenir plus d'informations sur les types de valeurs.

Créer des déclencheurs pour les fonctions

Cliquez sur l'onglet pour obtenir des instructions concernant l'utilisation de l'outil de votre choix.

Console

Lorsque vous utilisez la console Google Cloud pour créer une fonction, vous pouvez également ajouter un déclencheur à votre fonction. Pour créer un déclencheur pour votre fonction, procédez comme suit :

  1. Dans la console Google Cloud , accédez à Cloud Run :

    Accédez à Cloud Run

  2. Cliquez sur Écrire une fonction, puis saisissez les détails de la fonction. Pour en savoir plus sur la configuration des fonctions lors du déploiement, consultez Déployer des fonctions.

  3. Dans la section Déclencheur, cliquez sur Ajouter un déclencheur.

  4. Sélectionnez Déclencheur Firestore.

  5. Dans le volet Déclencheur Eventarc, modifiez les détails du déclencheur comme suit :

    1. Saisissez un nom pour le déclencheur dans le champ Nom du déclencheur ou utilisez le nom par défaut.

    2. Sélectionnez un type de déclencheur dans la liste :

      • Sources Google pour spécifier des déclencheurs pour Pub/Sub, Cloud Storage, Firestore et d'autres fournisseurs d'événements Google.

      • Tiers : pour intégrer des fournisseurs autres que Google qui proposent une source Eventarc. Pour en savoir plus, consultez Événements tiers dans Eventarc.

    3. Sélectionnez Firestore dans la liste Fournisseur d'événements pour choisir un produit qui fournit le type d'événement pour déclencher votre fonction. Pour obtenir la liste des fournisseurs d'événements, consultez Fournisseurs et destinations d'événements.

    4. Sélectionnez type=google.cloud.firestore.document.v1.created dans la liste Type d'événement. La configuration du déclencheur varie en fonction du type d'événement accepté: Pour en savoir plus, consultez Types d'événements.

    5. Dans la section "Filtres", sélectionnez une base de données, des valeurs d'opération et d'attribut, ou utilisez les sélections par défaut.

    6. Si le champ Région est activé, sélectionnez un emplacement pour le déclencheur Eventarc. En général, l'emplacement d'un déclencheur Eventarc doit correspondre à celui de la ressource Google Cloud dont vous souhaitez surveiller les événements. Dans la plupart des scénarios, vous devez également déployer votre fonction dans la même région. Consultez la section Comprendre les emplacements Eventarc pour en savoir plus sur les emplacements des déclencheurs Eventarc.

    7. Dans le champ Compte de service, sélectionnez un compte de service. Les déclencheurs Eventarc sont associés à des comptes de service, destinés à être utilisés comme identité lors de l'appel de votre fonction. Le compte de service de votre déclencheur Eventarc doit être autorisé à appeler votre fonction. Par défaut, Cloud Run utilise le compte de service Compute Engine par défaut.

    8. Vous pouvez éventuellement spécifier le chemin d'URL du service auquel envoyer la requête entrante. Il s'agit du chemin relatif sur le service de destination auquel les événements du déclencheur doivent être envoyés. Par exemple: /, /route, route et route/subroute.

  6. Une fois les champs obligatoires renseignés, cliquez sur Enregistrer le déclencheur.

gcloud

Lorsque vous créez une fonction à l'aide de gcloud CLI, vous devez d'abord déployer votre fonction, puis créer un déclencheur. Pour créer un déclencheur pour votre fonction, procédez comme suit :

  1. Exécutez la commande suivante dans le répertoire contenant l'exemple de code pour déployer votre fonction :

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

    Remplacez :

    • FUNCTION par le nom de la fonction que vous déployez. Vous pouvez omettre ce paramètre, mais dans ce cas le nom vous sera demandé.

    • FUNCTION_ENTRYPOINT par le point d'entrée de votre fonction dans votre code source. Il s'agit du code que Cloud Run exécute lorsque votre fonction s'exécute. La valeur de cette option doit être un nom de fonction ou un nom de classe complet qui existe dans votre code source.

    • BASE_IMAGE_ID par l'environnement d'image de base de votre fonction. Pour en savoir plus sur les images de base et les packages inclus dans chaque image, consultez Images de base de l'environnement d'exécution.

    • REGION par la région dans laquelle vous souhaitez déployer votre fonction. Google CloudPar exemple, europe-west1.

  2. Exécutez la commande suivante pour créer un déclencheur qui filtre les événements :

    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
    

    Remplacez :

    • TRIGGER_NAME par le nom de votre déclencheur.

    • EVENTARC_TRIGGER_LOCATION avec l'emplacement du déclencheur Eventarc. En général, l'emplacement d'un déclencheur Eventarc doit correspondre à celui de la ressource Google Cloud dont vous souhaitez surveiller les événements. Dans la plupart des scénarios, vous devez également déployer votre fonction dans la même région. Pour en savoir plus, consultez la page Emplacements Eventarc.

    • FUNCTION par le nom de la fonction que vous déployez.

    • REGION par la région Cloud Run de la fonction.

    • PROJECT_NUMBER par le numéro de votre Google Cloud projet. Les déclencheurs Eventarc sont associés à des comptes de service, destinés à être utilisés comme identité lors de l'appel de votre fonction. Le compte de service de votre déclencheur Eventarc doit être autorisé à appeler votre fonction. Par défaut, Cloud Run utilise le compte de service Compute par défaut.

    Chaque option event-filters spécifie un type d'événement. La fonction ne se déclenche que lorsqu'un événement répond à tous les critères spécifiés dans ses options event-filters. Chaque déclencheur doit comporter un indicateur event-filters spécifiant un type d'événement compatible, tel qu'un nouveau document écrit dans Firestore ou un fichier importé dans Cloud Storage. Vous ne pouvez pas modifier le type de filtre d'événements après sa création. Pour modifier le type de filtre d'événement, vous devez créer un déclencheur et supprimer l'ancien. Facultatif : vous pouvez répéter l'option --event-filters avec un filtre compatible au format ATTRIBUTE=VALUE pour ajouter d'autres filtres.

Terraform

Pour créer un déclencheur Eventarc pour une fonction Cloud Run, consultez Créer un déclencheur à l'aide de Terraform.

Exemples

Les exemples suivants décrivent comment utiliser les événements du mode natif Firestore pour déclencher une fonction Cloud Run.

Exemple 1 : Fonction Hello Firestore

L'exemple suivant imprime les champs d'un événement déclencheur 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));
    }
}

Déployer la fonction

Pour déployer la fonction Hello Firestore, exécutez la commande suivante :

Si ce n'est pas déjà fait, configurez votre base de données Firestore.

Pour déployer la fonction, consultez Créer des déclencheurs pour les fonctions.

Tester la fonction

Pour tester la fonction Hello Firestore, configurez une collection appelée users dans votre base de données Firestore :

  1. Dans la console Google Cloud , accédez à la page "Bases de données Firestore" :

    Accéder à Firestore

  2. Cliquez sur Commencer une collection.

  3. Spécifiez users comme ID de collection.

  4. Pour commencer à ajouter le premier document de la collection, sous Ajouter le premier document, acceptez l'ID de document généré automatiquement.

  5. Ajoutez au moins un champ pour le document, en spécifiant un nom et une valeur. Par exemple, dans Nom du champ, saisissez username et, dans Valeur du champ, saisissez rowan.

  6. Lorsque vous avez terminé, cliquez sur Save (Enregistrer).

    Cette action crée un document, ce qui va donc déclencher votre fonction.

  7. Pour vérifier que votre fonction a été déclenchée, cliquez sur le nom de la fonction associée dans la page Présentation de Cloud Run de la console Google Cloud pour ouvrir la page Informations sur le service.

  8. Accédez à l'onglet Journaux et recherchez cette chaîne :

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

Exemple 2 : Fonction de conversion en majuscules

L'exemple suivant récupère la valeur ajoutée par l'utilisateur, convertit la chaîne à cet emplacement en majuscules, et remplace la valeur par la chaîne en majuscules :

Node.js

Utilisez protobufjs pour décoder les données d'événement. Incluez la propriété google.events.cloud.firestore.v1data.proto dans votre source.

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

Déployer la fonction

Pour déployer la fonction Convert to Uppercase, exécutez la commande suivante :

Si ce n'est pas déjà fait, configurez votre base de données Firestore.

Pour déployer la fonction, consultez Créer des déclencheurs pour les fonctions.

Tester la fonction

Pour tester la fonction Convert to Uppercase que vous venez de déployer, configurez une collection appelée messages dans votre base de données Firestore :

  1. Dans la console Google Cloud , accédez à la page "Bases de données Firestore" :

    Accéder à Firestore

  2. Cliquez sur Commencer une collection.

  3. Spécifiez messages comme ID de collection.

  4. Pour commencer à ajouter le premier document de la collection, sous Ajouter le premier document, acceptez l'ID de document généré automatiquement.

  5. Pour déclencher votre fonction déployée, ajoutez un document dont le Nom du champ est original et la Valeur du champ est minka.

  6. Lorsque vous enregistrez le document, le mot en minuscules du champ de valeur est converti en majuscules.

    Si vous modifiez ensuite la valeur du champ pour y faire figurer des lettres minuscules, la fonction va à nouveau être déclenchée, ce qui va convertir toutes les lettres minuscules en majuscules.

Limites pour les fonctions

  • L'ordre n'est pas garanti. Les modifications rapides peuvent déclencher des appels de fonctions dans un ordre inattendu.
  • Bien que les événements soient diffusés au moins une fois, un même événement peut produire plusieurs appels de fonction. Évitez de dépendre de procédés dits "exactement une fois" et écrivez des fonctions idempotentes.
  • Un déclencheur est associé à une seule base de données. Vous ne pouvez pas créer de déclencheur qui correspond à plusieurs bases de données.
  • La suppression d'une base de données ne supprime pas automatiquement les déclencheurs de cette base de données. Le déclencheur cesse de diffuser des événements, mais continue d'exister jusqu'à ce que vous le supprimiez.