Amplía Cloud Firestore con Cloud Functions (2nd gen)

Con Cloud Functions, puedes implementar código para administrar eventos que se activan con los cambios en tu base de datos de Cloud Firestore. Esto te permite agregar con facilidad funciones del servidor a tu app, sin ejecutar tus propios servidores.

Cloud Functions (2nd gen)

Con la tecnología de Cloud Run y Eventarc, Cloud Functions para Firebase (2ª gen.) te ofrece una infraestructura más potente, un control avanzado sobre el rendimiento y la escalabilidad, y más control del tiempo de ejecución de las funciones. Si quieres obtener más información sobre la 2ª gen., consulta Cloud Functions para Firebase (2ª gen.). Para obtener más información sobre la 1ª gen., consulta Amplía Cloud Firestore con Cloud Functions.

Activadores de funciones de Cloud Firestore

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

Node.js

Tipo de evento Activador
onDocumentCreated Se activa cuando se escribe en un documento por primera vez.
onDocumentUpdated Se activa cuando un documento ya existe y se cambia uno de sus valores.
onDocumentDeleted Se activa cuando se borra un documento.
onDocumentWritten Se activa con onDocumentCreated, onDocumentUpdated o onDocumentDeleted.

Python (vista previa)

Tipo de evento Activador
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 se cambia uno de sus valores.
on_document_deleted Se activa cuando se borra un documento.
on_document_written Se activa con on_document_created, on_document_updated o on_document_deleted.

Los eventos de Cloud Firestore se activan solo cuando ocurren cambios en los documentos. La actualización de un documento de Cloud Firestore en la que no se modifican los datos (una escritura no-op) no genera un evento de actualización ni de escritura. No es posible agregar eventos a campos específicos.

Si aún no tienes un proyecto habilitado para Cloud Functions para Firebase, lee Primeros pasos con Cloud Functions para Firebase (2ª gen.) y configura el proyecto de Cloud Functions para Firebase.

Escribe funciones activadas por Cloud Firestore

Define un activador de función

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

Node.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 acceso de los documentos pueden hacer referencia a un documento específico o un patrón comodín.

Especifica un solo documento

Si deseas activar un evento para cualquier cambio que se realice a un documento específico, puedes usar la siguiente función.

Node.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:

Especifica un grupo de documentos mediante comodines

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

Node.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 algún campo en cualquier documento de users, coincide con un comodín llamado userId.

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

Las coincidencias de comodines se extraen de la ruta del documento y se almacenan en event.params. Puedes definir tantos comodines como desees para sustituir los IDs explícitos de colección o de documento, por ejemplo:

Node.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"}

Los activadores siempre deben apuntar a un documento, incluso si usas 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 apunta a un documento.

Activadores de eventos

Activa una función cuando se cree un documento nuevo

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

Node.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 ...

Activa una función cuando se actualice un documento

También puedes activar una función para que se active cuando se actualice un documento. Esta función de ejemplo se activa cada vez que un usuario cambia su perfil:

Node.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 ...

Activa una función cuando se borre un documento

Además, puedes activar una función cuando se borre un documento. Esta función de ejemplo se activa cuando un usuario borra su perfil:

Node.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 ...

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

Si no te interesa el tipo de evento que se activa, puedes detectar todos los cambios en un documento de Cloud Firestore con el activador de eventos “documento escrito”. Esta función de ejemplo se activa si se crea, actualiza o borra un usuario:

Node.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, esta proporciona una instantánea de los datos relacionados con el evento. Puedes usar esta instantánea para leer el documento que activó el evento o escribir en él, o bien usar el SDK de Firebase Admin para acceder a otras partes de tu base de datos.

Datos de eventos

Lee datos

Cuando se activa una función, es posible que desees obtener datos de un documento antes o después de que se lo actualice. Para obtener los datos previos, puedes usar 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.

Node.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()

Puedes acceder a las propiedades del mismo modo que con cualquier otro objeto. También puedes utilizar la función get para acceder a campos específicos:

Node.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"]

Escribe datos

Cada invocación de función está asociada a un documento específico de tu base de datos de Cloud Firestore. Puedes acceder a ese documento en la instantánea que se muestra a tu función.

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

Node.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 activador

Cloud Functions se ejecuta en entornos de confianza, lo que significa que cuenta con autorización como una cuenta de servicio en tu proyecto. Puedes realizar operaciones de lectura y escritura con el SDK de Firebase Admin:

Node.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

Ten en cuenta las siguientes limitaciones de los activadores de Cloud 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 la mecánica de entrega de eventos exactamente una vez y escribe funciones idempotentes.
  • Cloud 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 Cloud 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.