Activadores de Firestore (2ª gen.)

Puedes configurar tus Cloud Functions para que se activen con eventos en una base de datos de Firestore. Una vez activada, tu función puede leer y actualizar una base de datos de Firestore en respuesta a estos eventos a través de las APIs de Firestore y las bibliotecas cliente.

En un ciclo de vida típico, una función de Firestore hace lo siguiente:

  1. Espera a que ocurran cambios en un documento en particular.

  2. Se activa cuando ocurre un evento y realiza sus tareas.

  3. Recibe un objeto de datos con una instantánea del documento afectado. En el caso de los eventos write o update, el objeto de datos contiene instantáneas que representan el estado del documento antes y después del evento de activación.

Tipos de eventos

Firestore admite eventos create, update, delete y write. El evento write comprende todas las modificaciones que se realizan a un documento.

Tipo de evento Activador
google.cloud.firestore.document.v1.created (predeterminado) Se activa cuando se escribe en un documento por primera vez.
google.cloud.firestore.document.v1.updated Se activa cuando un documento ya existe y se cambia uno de sus valores.
google.cloud.firestore.document.v1.deleted Se activa cuando se borra un documento con datos.
google.cloud.firestore.document.v1.written Se activa cuando se crea, actualiza o borra un documento.

Los comodines se escriben en los activadores con llaves, como se muestra en el siguiente ejemplo: "projects/YOUR_PROJECT_ID/databases/(default)/documents/collection/{document_wildcard}"

Especifica la ruta del documento

Para activar tu función, especifica una ruta de acceso al documento que deseas escuchar. La ruta de acceso al documento debe estar en el mismo proyecto de Google Cloud que la función.

A continuación, se muestran algunos ejemplos de rutas de acceso de documentos válidas:

  • users/marie: Activador válido. Supervisa un solo documento, /users/marie.

  • users/{username}: activador válido. Supervisa todos los documentos del usuario. Los comodines se usan para supervisar todos los documentos de la colección.

  • users/{username}/addresses: activador no válido. Se refiere a la subcolección addresses, no a un documento.

  • users/{username}/addresses/home: activador válido. Supervisa el documento de dirección particular de todos los usuarios.

  • users/{username}/addresses/{addressId}: activador válido. Supervisa todos los documentos de dirección.

  • users/{user=**}: Activador válido. Supervisa todos los documentos del usuario y los documentos de las subcolecciones de cada documento del usuario, como /users/userID/address/home o /users/userID/phone/work.

Comodines y parámetros

Si no conoces el documento específico que deseas supervisar, usa un {wildcard} en lugar del ID del documento:

  • users/{username} escucha cambios en todos los documentos del usuario.

En este ejemplo, cuando se cambia cualquier campo en los documentos de users, coincide con un comodín llamado {username}.

Si un documento de users tiene subcolecciones y se modifica un campo de uno de los documentos en ellas, no se activará el comodín {username}. Si tu objetivo es responder también a los eventos en las subcolecciones, usa el comodín de varios segmentos {username=**}.

Las coincidencias de comodines se extraen de las rutas de acceso de documentos. Puedes definir tantos comodines como desees para sustituir los IDs explícitos de colección o documento. Puedes usar hasta un comodín de varios segmentos, como {username=**}.

Estructuras de eventos

Este activador invoca tu función con un evento similar al siguiente:

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

Cada objeto Document contiene uno o más objetos Value. Consulta la documentación de Value para obtener referencias de tipo. Esto resulta muy útil si usas un lenguaje escrito (como Go) para escribir tus funciones.

Configura la base de datos de Firestore

Necesitas una base de datos de Firestore para probar las muestras en este documento. Debe estar en su lugar antes de implementar las funciones. Si aún no está lista la base de datos de Firestore, crea una de la siguiente manera:

  1. Ir a la página de Datos de Firestore.

  2. Haz clic en Elegir modo nativo.

  3. Elige la región (ubicación) en la que reside tu base de datos. Esta elección es permanente.

  4. Haz clic en Crear base de datos.

El modelo de datos de Firestore consta de colecciones que contienen documentos. Cada documento contiene un conjunto de pares clave-valor.

Las funciones que creas en este instructivo se activan cuando realizas cambios en un documento dentro de una colección especificada.

Ejemplo 1: Función Hola Firestore

En la siguiente Cloud Function de muestra, se imprimen los campos de un evento de activación de Firestore:

Node.js

Usa protobufjs para decodificar los datos del evento. Incluye el google.events.cloud.firestore.v1 data.proto en la fuente.

/**
 * 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 err := proto.Unmarshal(event.Data(), &data); 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 firestorEventData = 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(firestorEventData.getOldValue().toString());

    logger.info("New value:");
    logger.info(firestorEventData.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));
    }
}

Implementa la función Hola Firestore

  1. Si aún no está lista, configura la base de datos de Firestore.

  2. Para ejecutar la función de Hola Firestore con un activador de Firestore, ejecuta el siguiente comando en el directorio que contiene el código de muestra (o, en el caso de Java, el archivo pom.xml):

    gcloud functions deploy FUNCTION_NAME \
    --gen2 \
    --runtime=RUNTIME \
    --region=REGION \
    --trigger-location=TRIGGER REGION \
    --source=. \
    --entry-point=ENTRY_POINT \
    --trigger-event-filters=type=google.cloud.firestore.document.v1.written \
    --trigger-event-filters=database='(default)' \
    --trigger-event-filters-path-pattern=document='users/{username}'
    

    Reemplaza lo siguiente:

    • FUNCTION_NAME: un nombre para la función implementada.
    • RUNTIME: el entorno de ejecución de lenguaje que usa tu función.
    • REGION: la región en la que se implementará la función.
    • TRIGGER_REGION: La ubicación del activador, que debe ser la misma que la región de la base de datos de Firestore.
    • ENTRY_POINT: el punto de entrada a tu función en tu código fuente. Este es el código que se ejecuta cuando se ejecuta tu función.

    Usa los otros campos tal como están:

    • --trigger-event-filters=type=google.cloud.firestore.document.v1.written especifica que la función se activa cuando se crea, actualiza o borra un documento, según el tipo de evento google.cloud.firestore.document.v1.written.
    • --trigger-event-filters=database='(default)' especifica la base de datos de Firebase. Para ver el nombre de la base de datos predeterminada, usa (default).
    • --trigger-event-filters-path-pattern=document='users/{username}' proporciona el patrón de ruta de acceso de los documentos que deben supervisarse para detectar cambios relevantes. En este patrón de ruta de acceso, se indica que se deben supervisar todos los documentos de la colección users. Para obtener más información, consulta Información sobre los patrones de ruta de acceso.

Prueba la función Hola Firestore

Para probar la función Hola Firestore, configura una colección llamada users en tu base de datos de Firestore:

  1. En la página de datos de Firestore, haz clic en Iniciar una colección.

  2. Especifica users como el ID de colección.

  3. Para empezar a agregar el primer documento de la colección, en Agregar su primer documento, acepta el ID de documento generado de forma automática.

  4. Agrega al menos un campo para el documento y especifica un nombre y un valor. En este ejemplo, el nombre es “username” y el valor es “rowan:”

    Captura de pantalla que muestra cómo crear una colección de Firestore

  5. Cuando termines, haz clic en Guardar.

    Esta acción crea un documento nuevo, lo que activa tu función.

  6. Para confirmar que tu función se activó, haz clic en el nombre vinculado de la función en la consola de Google Cloud.Página de resumen de Cloud Functions para abrir la página Detalles de la función.

  7. Abre la pestaña Registros y busca esta cadena:

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

Ejemplo 2: función Convertir en mayúsculas

Este ejemplo recupera el valor que agrega el usuario, se convierte la cadena en esa ubicación a mayúscula y se reemplaza el valor por la cadena en mayúscula:

Node.js

Usa protobufjs para decodificar los datos del evento. Incluye el google.events.cloud.firestore.v1 data.proto en la fuente.

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 err := proto.Unmarshal(e.Data(), &data); 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);
    }
}

Implementa la función Convertir en mayúsculas

  1. Si aún no está lista, configura la base de datos de Firestore.

  2. Usa el siguiente comando para implementar una función que se activa a través de eventos de escritura en el documento companies/{CompanyId}:

    gcloud functions deploy FUNCTION_NAME \
    --gen2 \
    --runtime=RUNTIME \
    --trigger-location=TRIGGER REGION \
    --region=REGION \
    --source=. \
    --entry-point=ENTRY_POINT \
    --trigger-event-filters=type=google.cloud.firestore.document.v1.written \
    --trigger-event-filters=database='(default)' \
    --trigger-event-filters-path-pattern=document='messages/{pushId}'
    

    Reemplaza lo siguiente:

    • FUNCTION_NAME: un nombre para la función implementada.
    • RUNTIME: el entorno de ejecución de lenguaje que usa tu función.
    • REGION: la región en la que se implementará la función.
    • TRIGGER_REGION: La ubicación del activador, que debe ser la misma que la región de la base de datos de Firestore.
    • ENTRY_POINT: el punto de entrada a tu función en tu código fuente. Este es el código que se ejecuta cuando se ejecuta tu función.

    Usa los otros campos tal como están:

    • --trigger-event-filters=type=google.cloud.firestore.document.v1.written especifica que la función se activa cuando se crea, actualiza o borra un documento, según el tipo de evento google.cloud.firestore.document.v1.written.
    • --trigger-event-filters=database='(default)' especifica la base de datos de Firestore. Para ver el nombre de la base de datos predeterminada, usa (default).
    • --trigger-event-filters-path-pattern=document='messages/{pushId}' proporciona el patrón de ruta de acceso de los documentos que deben supervisarse para detectar cambios relevantes. En este patrón de ruta de acceso, se indica que se deben supervisar todos los documentos de la colección messages. Para obtener más información, consulta Información sobre los patrones de ruta de acceso.

Prueba la función Convertir en mayúsculas

Para probar la función Convertir en mayúsculas que acabas de implementar, configura una colección llamada messages en la base de datos de Firestore:

  1. Ir a la página de Datos de Firestore.

  2. Haz clic en Iniciar una colección.

  3. Especifica messages como el ID de colección.

  4. Para empezar a agregar el primer documento de la colección, en Agregar su primer documento, acepta el ID de documento generado de forma automática.

  5. Para activar la función implementada, agrega un documento en el que el nombre del campo sea “original” y el valor del campo sea una palabra en minúscula, por ejemplo:

    Captura de pantalla que muestra cómo crear una colección de Firestore

  6. Cuando guardes el documento, verás la palabra en minúsculas en el campo de valor que convierte a mayúsculas.

    Si luego editas el valor del campo para que contenga letras minúsculas, se activará la función de nuevo y se convertirán todas las letras minúsculas en mayúsculas.

Limitaciones

Ten en cuenta las siguientes limitaciones para los activadores de Firestore para Cloud Functions:

  • No se garantiza el ordenamiento. Los cambios rápidos pueden activar invocaciones de funciones en un orden inesperado.
  • Los eventos se entregan al menos una vez, pero un solo evento puede dar lugar a varias invocaciones de funciones. Evita depender de mecanismos solo una vez exactos y escribe funciones idempotentes.
  • Firestore en modo Datastore requiere Cloud Functions (2nd gen). Cloud Functions (1ª gen.) no es compatible con el modo Datastore.
  • Cloud Functions (1st gen) solo funciona con bases de datos “(predeterminadas)” y no es compatible con las bases de datos con nombre de Firestore. Usa Cloud Functions (2nd gen) para configurar eventos para las bases de datos con nombre.
  • Un activador se asocia con una sola base de datos. No puedes crear un activador que coincida con varias bases de datos.
  • Cuando se borra una base de datos, no se borra automáticamente ningún activador de la base de datos. El activador deja de entregar eventos, pero sigue existiendo hasta que lo borras.
  • Si un evento coincidente excede el tamaño máximo de la solicitud, es posible que el evento no se entregue a Cloud Functions (1st gen).
    • Los eventos que no se entregan debido al tamaño de la solicitud se registran en los registros de la plataforma y se consideran para el uso de registros del proyecto.
    • Puedes encontrar estos registros en el Explorador de registros con el mensaje “El evento no se puede entregar a Cloud Function debido a que el tamaño supera el límite de 1ª gen... de gravedad error”. Puedes encontrar el nombre de la función en el campo functionName. Si el campo receiveTimestamp aún está dentro de una hora a partir de ahora, puedes leer el documento en cuestión con una instantánea antes y después de la marca de tiempo para inferir el contenido real del evento.
    • Para evitar esa cadencia, puedes hacer lo siguiente:
      • Migra y actualiza a Cloud Functions (2nd gen)
      • Reduce el tamaño del documento
      • Borra la función de Cloud Functions en cuestión
    • Puedes desactivar el registro mediante exclusiones, pero ten en cuenta que los eventos infractores no se entregarán.