Amplíe Cloud Firestore con Cloud Functions (segunda generación)

Con Cloud Functions, puedes implementar código para manejar eventos desencadenados por cambios en tu base de datos de Cloud Firestore. Esto le permite agregar fácilmente funcionalidad del lado del servidor a su aplicación sin ejecutar sus propios servidores.

Funciones en la nube (segunda generación)

Con tecnología de Cloud Run y ​​Eventarc , Cloud Functions para Firebase (segunda generación) le brinda una infraestructura más poderosa, control avanzado sobre el rendimiento y la escalabilidad, y más control del tiempo de ejecución de las funciones. Para obtener más información sobre la 2.ª generación, consulte Cloud Functions para Firebase (2.ª generación) . Para obtener más información sobre la primera generación, consulte Ampliar Cloud Firestore con funciones de nube .

Activadores de la función Cloud Firestore

El SDK de Cloud Functions para Firebase exporta los siguientes activadores de eventos de Cloud Firestore para permitirte crear controladores vinculados a eventos específicos de Cloud Firestore:

Nodo.js

Tipo de evento Desencadenar
onDocumentCreated Se activa cuando se escribe en un documento por primera vez.
onDocumentUpdated Se activa cuando un documento ya existe y tiene algún valor modificado.
onDocumentDeleted Se activa cuando se elimina un documento.
onDocumentWritten Se activa cuando se activa onDocumentCreated , onDocumentUpdated o onDocumentDeleted .

Python (vista previa)

Tipo de evento Desencadenar
on_document_created Se activa cuando se escribe en un documento por primera vez.
on_document_updated Se activa cuando un documento ya existe y tiene algún valor modificado.
on_document_deleted Se activa cuando se elimina un documento.
on_document_written Se activa cuando se activa on_document_created , on_document_updated o on_document_deleted .

Los eventos de Cloud Firestore se activan solo cuando se realizan cambios en los documentos. Una actualización de un documento de Cloud Firestore donde los datos no se modifican (una escritura no operativa) no genera un evento de actualización o escritura. No es posible agregar eventos a campos específicos.

Si aún no tiene un proyecto habilitado para Cloud Functions para Firebase, lea Introducción a Cloud Functions para Firebase (segunda generación) para configurar y configurar su proyecto de Cloud Functions para Firebase.

Escribir funciones activadas por Cloud Firestore

Definir un disparador de función

Para definir un activador de Cloud Firestore, especifica una ruta de documento y un tipo de evento:

Nodo.js

import {
  onDocumentWritten,
  onDocumentCreated,
  onDocumentUpdated,
  onDocumentDeleted,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("my-collection/{docId}", (event) => {
   /* ... */ 
});

Python (vista previa)

from firebase_functions.firestore_fn import (
  on_document_created,
  on_document_deleted,
  on_document_updated,
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_created(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot]) -> None:

Las rutas de documentos pueden hacer referencia a un documento específico o a un patrón comodín .

Especificar un solo documento

Si desea activar un evento para cualquier cambio en un documento específico, puede utilizar la siguiente función.

Nodo.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("users/marie", (event) => {
  // Your code here
});

Python (vista previa)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/marie")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:

Especificar un grupo de documentos usando comodines

Si desea adjuntar un activador a un grupo de documentos, como cualquier documento de una colección determinada, utilice un {wildcard} en lugar del ID del documento:

Nodo.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("users/{userId}", (event) => {
  // If we set `/users/marie` to {name: "Marie"} then
  // event.params.userId == "marie"
  // ... and ...
  // event.data.after.data() == {name: "Marie"}
});

Python (vista previa)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # If we set `/users/marie` to {name: "Marie"} then
  event.params["userId"] == "marie"  # True
  # ... and ...
  event.data.after.to_dict() == {"name": "Marie"}  # True

En este ejemplo, cuando se cambia cualquier campo de cualquier documento en users , coincide con un comodín llamado userId .

Si un documento en users tiene subcolecciones y se cambia un campo en uno de los documentos de esas subcolecciones, el comodín userId no se activa.

Las coincidencias con comodines se extraen de la ruta del documento y se almacenan en event.params . Puede definir tantos comodines como desee para sustituir ID de documentos o colecciones explícitas, por ejemplo:

Nodo.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("users/{userId}/{messageCollectionId}/{messageId}", (event) => {
    // If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
    // event.params.userId == "marie";
    // event.params.messageCollectionId == "incoming_messages";
    // event.params.messageId == "134";
    // ... and ...
    // event.data.after.data() == {body: "Hello"}
});

Python (vista previa)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/{userId}/{messageCollectionId}/{messageId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
  event.params["userId"] == "marie"  # True
  event.params["messageCollectionId"] == "incoming_messages"  # True
  event.params["messageId"] == "134"  # True
  # ... and ...
  event.data.after.to_dict() == {"body": "Hello"}

Su activador siempre debe apuntar a un documento, incluso si utiliza un comodín. Por ejemplo, users/{userId}/{messageCollectionId} no es válido porque {messageCollectionId} es una colección. Sin embargo, users/{userId}/{messageCollectionId}/{messageId} es válido porque {messageId} siempre apuntará a un documento.

Activadores de eventos

Activar una función cuando se crea un nuevo documento

Puede activar una función para que se active cada vez que se crea un nuevo documento en una colección. Esta función de ejemplo se activa cada vez que se agrega un nuevo perfil de usuario:

Nodo.js

import {
  onDocumentCreated,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.createuser = onDocumentCreated("users/{userId}", (event) => {
    // Get an object representing the document
    // e.g. {'name': 'Marie', 'age': 66}
    const snapshot = event.data;
    if (!snapshot) {
        console.log("No data associated with the event");
        return;
    }
    const data = snapshot.data();

    // access a particular field as you would any JS property
    const name = data.name;

    // perform more operations ...
});

Python (vista previa)

from firebase_functions.firestore_fn import (
  on_document_created,
  Event,
  DocumentSnapshot,
)

@on_document_created(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot]) -> None:
  # Get a dictionary representing the document
  # e.g. {'name': 'Marie', 'age': 66}
  new_value = event.data.to_dict()

  # Access a particular field as you would any dictionary
  name = new_value["name"]

  # Perform more operations ...

Activar una función cuando se actualiza un documento

También puede activar una función para que se active cuando se actualiza un documento. Esta función de ejemplo se activa si un usuario cambia su perfil:

Nodo.js

import {
  onDocumentUpdated,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.updateuser = onDocumentUpdated("users/{userId}", (event) => {
    // Get an object representing the document
    // e.g. {'name': 'Marie', 'age': 66}
    const newValue = event.data.after.data();

    // access a particular field as you would any JS property
    const name = newValue.name;

    // perform more operations ...
});

Python (vista previa)

from firebase_functions.firestore_fn import (
  on_document_updated,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # Get a dictionary representing the document
  # e.g. {'name': 'Marie', 'age': 66}
  new_value = event.data.after.to_dict()

  # Access a particular field as you would any dictionary
  name = new_value["name"]

  # Perform more operations ...

Activar una función cuando se elimina un documento

También puede activar una función cuando se elimina un documento. Esta función de ejemplo se activa cuando un usuario elimina su perfil de usuario:

Nodo.js

import {
  onDocumentDeleted,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.deleteuser = onDocumentDeleted("users/{userId}", (event) => {
    // Get an object representing the document
    // e.g. {'name': 'Marie', 'age': 66}
    const snap =  event.data;
    const data =  snap.data();

    // perform more operations ...
});

Python (vista previa)

from firebase_functions.firestore_fn import (
  on_document_deleted,
  Event,
  DocumentSnapshot,
)

@on_document_deleted(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot|None]) -> None:
  # Perform more operations ...

Activar una función para todos los cambios en un documento

Si no le importa el tipo de evento que se activa, puede escuchar todos los cambios en un documento de Cloud Firestore utilizando el activador de evento "documento escrito". Esta función de ejemplo se activa si se crea, actualiza o elimina un usuario:

Nodo.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.modifyuser = onDocumentWritten("users/{userId}", (event) => {
    // Get an object with the current document values.
    // If the document does not exist, it was deleted
    const document =  event.data.after.data();

    // Get an object with the previous document values
    const previousValues =  event.data.before.data();

    // perform more operations ...
});

Python (vista previa)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot | None]]) -> None:
  # Get an object with the current document values.
  # If the document does not exist, it was deleted.
  document = (event.data.after.to_dict()
              if event.data.after is not None else None)

  # Get an object with the previous document values.
  # If the document does not exist, it was newly created.
  previous_values = (event.data.before.to_dict()
                     if event.data.before is not None else None)

  # Perform more operations ...

Lectura y escritura de datos

Cuando se activa una función, proporciona una instantánea de los datos relacionados con el evento. Puede usar esta instantánea para leer o escribir en el documento que desencadenó el evento, o usar el SDK de Firebase Admin para acceder a otras partes de su base de datos.

Datos del evento

Lectura de datos

Cuando se activa una función, es posible que desee obtener datos de un documento que se actualizó u obtener los datos antes de la actualización. Puede obtener los datos anteriores utilizando event.data.before , que contiene la instantánea del documento antes de la actualización. De manera similar, event.data.after contiene el estado de la instantánea del documento después de la actualización.

Nodo.js

exports.updateuser2 = onDocumentUpdated("users/{userId}", (event) => {
    // Get an object with the current document values.
    // If the document does not exist, it was deleted
    const newValues =  event.data.after.data();

    // Get an object with the previous document values
    const previousValues =  event.data.before.data();
});

Python (vista previa)

@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # Get an object with the current document values.
  new_value = event.data.after.to_dict()

  # Get an object with the previous document values.
  prev_value = event.data.before.to_dict()

Puede acceder a las propiedades como lo haría en cualquier otro objeto. Alternativamente, puede utilizar la función get para acceder a campos específicos:

Nodo.js

// Fetch data using standard accessors
const age = event.data.after.data().age;
const name = event.data.after.data()['name'];

// Fetch data using built in accessor
const experience = event.data.after.data.get('experience');

Python (vista previa)

# Get the value of a single document field.
age = event.data.after.get("age")

# Convert the document to a dictionary.
age = event.data.after.to_dict()["age"]

Escribir datos

Cada invocación de función está asociada con un documento específico en su base de datos de Cloud Firestore. Puede acceder a ese documento en la instantánea devuelta a su función.

La referencia del documento incluye métodos como update() , set() y remove() para que pueda modificar el documento que activó la función.

Nodo.js

import { onDocumentUpdated } from "firebase-functions/v2/firestore";

exports.countnamechanges = onDocumentUpdated('users/{userId}', (event) => {
  // Retrieve the current and previous value
  const data = event.data.after.data();
  const previousData = event.data.before.data();

  // We'll only update if the name has changed.
  // This is crucial to prevent infinite loops.
  if (data.name == previousData.name) {
    return null;
  }

  // Retrieve the current count of name changes
  let count = data.name_change_count;
  if (!count) {
    count = 0;
  }

  // Then return a promise of a set operation to update the count
  return data.after.ref.set({
    name_change_count: count + 1
  }, {merge: true});

});

Python (vista previa)

@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
 # Get the current and previous document values.
 new_value = event.data.after
 prev_value = event.data.before

 # We'll only update if the name has changed.
 # This is crucial to prevent infinite loops.
 if new_value.get("name") == prev_value.get("name"):
     return

 # Retrieve the current count of name changes
 count = new_value.to_dict().get("name_change_count", 0)

 # Update the count
 new_value.reference.update({"name_change_count": count + 1})
 ```

Datos fuera del evento desencadenante

Las funciones de la nube se ejecutan en un entorno confiable. Están autorizadas como una cuenta de servicio en tu proyecto y puedes realizar lecturas y escrituras usando el SDK de Firebase Admin :

Nodo.js

const { initializeApp } = require('firebase-admin/app');
const { getFirestore, Timestamp, FieldValue } = require('firebase-admin/firestore');

initializeApp();
const db = getFirestore();

exports.writetofirestore = onDocumentWritten("some/doc", (event) => {
    db.doc('some/otherdoc').set({ ... });
  });

  exports.writetofirestore = onDocumentWritten('users/{userId}', (event) => {
    db.doc('some/otherdoc').set({
      // Update otherdoc
    });
  });

Python (vista previa)

from firebase_admin import firestore, initialize_app
import google.cloud.firestore

initialize_app()

@on_document_written(document="some/doc")
def myfunction(event: Event[Change[DocumentSnapshot | None]]) -> None:
  firestore_client: google.cloud.firestore.Client = firestore.client()
  firestore_client.document("another/doc").set({
      # ...
  })

Limitaciones

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

  • El pedido no está garantizado. Los cambios rápidos pueden desencadenar invocaciones de funciones en un orden inesperado.
  • Los eventos se entregan al menos una vez, pero un solo evento puede dar lugar a múltiples invocaciones de funciones. Evite depender de mecánicas de una sola vez y escriba funciones idempotentes .
  • Cloud Firestore en modo Datastore requiere Cloud Functions (segunda generación). Cloud Functions (1.ª generación) no admite el modo Datastore.
  • Cloud Functions (1.ª generación) solo funciona con la base de datos "(predeterminada)" y no admite bases de datos con nombre de Cloud Firestore. Utilice Cloud Functions (segunda generación) para configurar eventos para bases de datos con nombre.
  • Un disparador está asociado con una única base de datos. No puede crear un activador que coincida con varias bases de datos.
  • Eliminar una base de datos no elimina automáticamente ningún desencadenante de esa base de datos. El desencadenador deja de entregar eventos pero continúa existiendo hasta que lo elimina .
  • Si un evento coincidente excede el tamaño máximo de solicitud , es posible que el evento no se entregue a Cloud Functions (1.ª generación).
    • Los eventos que no se entregan debido al tamaño de la solicitud se registran en los registros de la plataforma y cuentan para el uso del registro del proyecto.
    • Puede encontrar estos registros en el Explorador de registros con el mensaje "El evento no se puede entregar a la función de la nube debido a que el tamaño excede el límite para la 1.ª generación..." de gravedad del error . Puede 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, puede inferir el contenido real del evento leyendo el documento en cuestión con una instantánea antes y después de la marca de tiempo.
    • Para evitar esa cadencia, puedes:
      • Migrar y actualizar a Cloud Functions (segunda generación)
      • Reducir el tamaño del documento
      • Eliminar las funciones de la nube en cuestión
    • Puede desactivar el registro mediante exclusiones , pero tenga en cuenta que los eventos infractores aún no se entregarán.