Extender Firestore con Cloud Functions
Con Cloud Functions, puedes desplegar código de Node.js para gestionar eventos activados por cambios en tu base de datos de Firestore. De esta forma, puedes añadir fácilmente funciones del lado del servidor a tu aplicación sin tener que ejecutar tus propios servidores.
Para ver ejemplos de casos prácticos, consulta ¿Qué puedo hacer con Cloud Functions? o el repositorio de GitHub Functions Samples.
Activadores de funciones de Firestore
El SDK de Cloud Functions para Firebase exporta un objeto functions.firestore
que te permite crear controladores vinculados a eventos específicos de Firestore.
Tipo de evento | Activador |
---|---|
onCreate |
Se activa cuando se escribe en un documento por primera vez. |
onUpdate |
Se activa cuando ya existe un documento y se cambia algún valor. |
onDelete |
Se activa cuando se elimina un documento con datos. |
onWrite |
Se activa cuando se activa onCreate , onUpdate o onDelete . |
Si aún no tienes ningún proyecto habilitado para Cloud Functions for Firebase, consulta el artículo Primeros pasos: escribe e implementa tus primeras funciones para configurar tu proyecto de Cloud Functions for Firebase.
Escribir funciones activadas por Firestore
Definir un activador de función
Para definir un activador de Firestore, especifica una ruta de documento y un tipo de evento:
Node.js
const functions = require('firebase-functions');
exports.myFunction = functions.firestore
.document('my-collection/{docId}')
.onWrite((change, context) => { /* ... */ });
Las rutas de documentos pueden hacer referencia a un documento específico o a un patrón comodín.
Especificar un solo documento
Si quieres activar un evento para cualquier cambio en un documento específico, puedes usar la siguiente función.
Node.js
// Listen for any change on document `marie` in collection `users` exports.myFunctionName = functions.firestore .document('users/marie').onWrite((change, context) => { // ... Your code here });
Especificar un grupo de documentos mediante comodines
Si quieres adjuntar un activador a un grupo de documentos, como cualquier documento de una colección determinada, usa un {wildcard}
en lugar del ID del documento:
Node.js
// Listen for changes in all documents in the 'users' collection exports.useWildcard = functions.firestore .document('users/{userId}') .onWrite((change, context) => { // If we set `/users/marie` to {name: "Marie"} then // context.params.userId == "marie" // ... and ... // change.after.data() == {name: "Marie"} });
En este ejemplo, cuando se cambia cualquier campo de cualquier documento de users
, coincide con un comodín llamado userId
.
Si un documento de users
tiene subcolecciones y se cambia un campo de uno de los documentos de esas subcolecciones, no se activa el comodín userId
.
Las coincidencias con comodines se extraen de la ruta del documento y se almacenan en context.params
.
Puedes definir tantos comodines como quieras para sustituir los IDs de colecciones o documentos explícitos. Por ejemplo:
Node.js
// Listen for changes in all documents in the 'users' collection and all subcollections exports.useMultipleWildcards = functions.firestore .document('users/{userId}/{messageCollectionId}/{messageId}') .onWrite((change, context) => { // If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then // context.params.userId == "marie"; // context.params.messageCollectionId == "incoming_messages"; // context.params.messageId == "134"; // ... and ... // change.after.data() == {body: "Hello"} });
Activadores de eventos
Activar una función cuando se cree un documento
Puedes activar una función para que se ejecute cada vez que se cree un documento en una colección
mediante un controlador onCreate()
con un comodín.
Esta función de ejemplo llama a createUser
cada vez que se añade un nuevo perfil de usuario:
Node.js
exports.createUser = functions.firestore .document('users/{userId}') .onCreate((snap, context) => { // Get an object representing the document // e.g. {'name': 'Marie', 'age': 66} const newValue = snap.data(); // access a particular field as you would any JS property const name = newValue.name; // perform desired operations ... });
Activar una función cuando se actualiza un documento
También puedes activar una función para que se ejecute cuando se actualice un documento mediante la función onUpdate()
con un comodín. Esta función de ejemplo llama a updateUser
si un usuario
cambia su perfil:
Node.js
exports.updateUser = functions.firestore .document('users/{userId}') .onUpdate((change, context) => { // Get an object representing the document // e.g. {'name': 'Marie', 'age': 66} const newValue = change.after.data(); // ...or the previous value before this update const previousValue = change.before.data(); // access a particular field as you would any JS property const name = newValue.name; // perform desired operations ... });
Activar una función cuando se elimine un documento
También puedes activar una función cuando se elimine un documento usando la función onDelete()
con un comodín. En este ejemplo, la función llama a deleteUser
cuando un usuario elimina su perfil:
Node.js
exports.deleteUser = functions.firestore .document('users/{userID}') .onDelete((snap, context) => { // Get an object representing the document prior to deletion // e.g. {'name': 'Marie', 'age': 66} const deletedValue = snap.data(); // perform desired operations ... });
Activar una función para todos los cambios de un documento
Si no te importa el tipo de evento que se activa, puedes monitorizar todos los cambios de un documento de Firestore mediante la función onWrite()
con un comodín. Esta función de ejemplo llama a modifyUser
si se crea, actualiza o elimina un usuario:
Node.js
exports.modifyUser = functions.firestore .document('users/{userID}') .onWrite((change, context) => { // Get an object with the current document value. // If the document does not exist, it has been deleted. const document = change.after.exists ? change.after.data() : null; // Get an object with the previous document value (for update or delete) const oldDocument = change.before.data(); // perform desired operations ... });
Leer y escribir datos
Cuando se activa una función, se proporciona una instantánea de los datos relacionados con el evento. Puedes usar esta instantánea para leer o escribir en el documento que ha activado el evento, o bien usar el SDK de administrador de Firebase para acceder a otras partes de tu base de datos.
Datos de eventos
Leer datos
Cuando se activa una función, es posible que quieras obtener datos de un documento que se haya actualizado o los datos anteriores a la actualización. Puedes obtener los datos anteriores mediante change.before.data()
, que contiene la captura del documento antes de la actualización.
Del mismo modo, change.after.data()
contiene el estado de la captura del documento después de la actualización.
Node.js
exports.updateUser2 = functions.firestore .document('users/{userId}') .onUpdate((change, context) => { // Get an object representing the current document const newValue = change.after.data(); // ...or the previous value before this update const previousValue = change.before.data(); });
Puede acceder a las propiedades como lo haría en cualquier otro objeto. También puedes usar la función get
para acceder a campos específicos:
Node.js
// Fetch data using standard accessors const age = snap.data().age; const name = snap.data()['name']; // Fetch data using built in accessor const experience = snap.get('experience');
Escribir datos
Cada invocación de función se asocia a un documento específico de tu base de datos de Firestore. Puedes acceder a ese documento como DocumentReference
en la propiedad ref
de la vista del día devuelta a tu función.
Este DocumentReference
procede del
SDK de Node.js de Firestore
e incluye métodos como update()
, set()
y remove()
para que puedas modificar fácilmente el documento que ha activado la función.
Node.js
// Listen for updates to any `user` document. exports.countNameChanges = functions.firestore .document('users/{userId}') .onUpdate((change, context) => { // Retrieve the current and previous value const data = change.after.data(); const previousData = change.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 change.after.ref.set({ name_change_count: count + 1 }, {merge: true}); });
Datos fuera del evento de activación
Cloud Functions se ejecuta en un entorno de confianza, lo que significa que se autoriza como una cuenta de servicio en tu proyecto. Puedes realizar lecturas y escrituras con el SDK de administrador de Firebase:
Node.js
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.writeToFirestore = functions.firestore
.document('some/doc')
.onWrite((change, context) => {
db.doc('some/otherdoc').set({ ... });
});
Limitaciones
Ten en cuenta las siguientes limitaciones de los activadores de Firestore para las funciones de Cloud Run:
- Las funciones de Cloud Run (1.ª gen.) requieren una base de datos "(default)" en el modo nativo de Firestore. No admite bases de datos con nombre de Firestore ni el modo Datastore. En estos casos, utiliza funciones de Cloud Run (2.ª gen.) para configurar los eventos.
- No se garantiza la realización del pedido. 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. No dependas de los mecanismos de entrega exactamente una vez y escribe funciones idempotentes.
- Firestore en el modo de Datastore requiere funciones de Cloud Run (2.ª gen.). Cloud Run Functions (1.ª gen.) no admite el modo Datastore.
- Un activador está asociado a una sola base de datos. No puedes crear un activador que coincida con varias bases de datos.
- Si eliminas una base de datos, no se eliminarán automáticamente los activadores de esa base de datos. El activador deja de enviar eventos, pero sigue existiendo hasta que lo eliminas.
- Si un evento coincidente supera el tamaño máximo de solicitud, es posible que no se envíe a las funciones de Cloud Run (1.ª gen.).
- Los eventos que no se entregan debido al tamaño de la solicitud se registran en los registros de la plataforma y se tienen en cuenta en el uso de registros del proyecto.
- Puedes encontrar estos registros en el Explorador de registros con el mensaje "Event cannot deliver to
Cloud function due to size exceeding the limit for 1st gen..." (No se puede enviar el evento a la función de Cloud porque el tamaño supera el límite de la primera generación...) de
error
gravedad. Puedes encontrar el nombre de la función en el campofunctionName
. Si el camporeceiveTimestamp
sigue estando a menos de una hora, puedes inferir el contenido del evento leyendo el documento en cuestión con una instantánea antes y después de la marca de tiempo. - Para evitar este tipo de cadencia, puedes hacer lo siguiente:
- Migrar y actualizar a Cloud Run Functions (2.ª gen.)
- Reducir el tamaño del documento
- Elimina las funciones de Cloud Run en cuestión.
- Puedes desactivar el registro mediante exclusiones, pero ten en cuenta que los eventos infractores seguirán sin enviarse.