Amplíe la base de datos en tiempo real con funciones en la nube


Con Cloud Functions, puedes manejar eventos en Firebase Realtime Database sin necesidad de actualizar el código del cliente. Cloud Functions le permite ejecutar operaciones de Realtime Database con privilegios administrativos completos y garantiza que cada cambio en Realtime Database se procese individualmente. Puede realizar cambios en la base de datos Firebase Realtime a través de la instantánea de datos o mediante el SDK de administración.

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

  1. Espera cambios en una ruta particular de Realtime Database.
  2. Se activa cuando ocurre un evento y realiza sus tareas.
  3. Recibe un objeto de datos que contiene una instantánea de los datos almacenados en esa ruta.

Puedes activar una función en respuesta a la escritura, creación, actualización o eliminación de nodos de la base de datos en Firebase Realtime Database. Para controlar cuándo se activa la función, especifique uno de los controladores de eventos y especifique la ruta de la base de datos en tiempo real donde escuchará los eventos.

Configuración de la ubicación de la función

La distancia entre la ubicación de una instancia de Realtime Database y la ubicación de la función puede crear una latencia de red significativa. Además, una falta de coincidencia entre regiones puede provocar un error en la implementación. Para evitar estas situaciones, especifique la ubicación de la función para que coincida con la ubicación de la instancia de la base de datos .

Manejo de eventos de bases de datos en tiempo real

Las funciones le permiten manejar eventos de bases de datos en tiempo real en dos niveles de especificidad; puede escuchar específicamente solo eventos de escritura, creación, actualización o eliminación, o puede escuchar cualquier cambio de cualquier tipo en una referencia.

Estos controladores para responder a eventos de Realtime Database están disponibles:

  • onValueWritten() Se activa cuando se crean, actualizan o eliminan datos en Realtime Database.
  • onValueCreated() Solo se activa cuando se crean datos en Realtime Database.
  • onValueUpdated() Solo se activa cuando los datos se actualizan en Realtime Database.
  • onValueDeleted() Solo se activa cuando se eliminan datos en Realtime Database.
  • on_value_written() Se activa cuando se crean, actualizan o eliminan datos en Realtime Database.
  • on_value_created() Solo se activa cuando los datos se crean en Realtime Database.
  • on_value_updated() Solo se activa cuando los datos se actualizan en Realtime Database.
  • on_value_deleted() Solo se activa cuando se eliminan datos en Realtime Database.

Importar módulos requeridos

En su fuente de funciones, debe importar los módulos SDK que desee utilizar. Para este ejemplo, es necesario importar los módulos HTTP y Realtime Database junto con el módulo Firebase Admin SDK para escribir en Realtime Database.

// The Cloud Functions for Firebase SDK to setup triggers and logging.
const {onRequest} = require("firebase-functions/v2/https");
const {onValueCreated} = require("firebase-functions/v2/database");
const {logger} = require("firebase-functions");

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require("firebase-admin");
admin
.initializeApp();
# The Cloud Functions for Firebase SDK to create Cloud Functions and set up triggers.
from firebase_functions import db_fn, https_fn

# The Firebase Admin SDK to access the Firebase Realtime Database.
from firebase_admin import initialize_app, db

app
= initialize_app()

Especificar instancia y ruta

Para controlar cuándo y dónde debe activarse su función, configure su función con una ruta y, opcionalmente, una instancia de Realtime Database. Si no especifica una instancia, la función escucha todas las instancias de Realtime Database en la región de la función. También puede especificar un patrón de instancia de Realtime Database para implementarlo en un subconjunto selectivo de instancias en la misma región.

Por ejemplo:

// All Realtime Database instances in default function region us-central1 at path "/user/{uid}"
// There must be at least one Realtime Database present in us-central1.
const onWrittenFunctionDefault = onValueWritten("/user/{uid}", (event) => {
 
// …
});

// Instance named "my-app-db-2", at path "/user/{uid}".
// The "my-app-db-2" instance must exist in this region.
const OnWrittenFunctionInstance = onValueWritten(
 
{
   
ref: "/user/{uid}",
    instance
: "my-app-db-2"
   
// This example assumes us-central1, but to set location:
   
// region: "europe-west1"
 
},
 
(event) => {
   
// …
 
}
);

// Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com.
// There must be at least one Realtime Database with "my-app-db-*" prefix in this region.
const onWrittenFunctionInstance = onValueWritten(
 
{
   
ref: "/user/{uid=*@gmail.com}",
    instance
: "my-app-db-*"
   
// This example assumes us-central1, but to set location:
   
// region: "europe-west1"
 
},
 
(event) => {
   
// …
 
}
);
# All Realtime Database instances in default function region us-central1 at path "/user/{uid}"
# There must be at least one Realtime Database present in us-central1.
@db_fn.on_value_written(r"/user/{uid}")
def onwrittenfunctiondefault(event: db_fn.Event[db_fn.Change]):
   
# ...
   
pass

# Instance named "my-app-db-2", at path "/user/{uid}".
# The "my-app-db-2" instance must exist in this region.
@db_fn.on_value_written(
    reference
=r"/user/{uid}",
    instance
="my-app-db-2",
   
# This example assumes us-central1, but to set location:
   
# region="europe-west1",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
   
# ...
   
pass

# Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com.
# There must be at least one Realtime Database with "my-app-db-*" prefix in this region.
@db_fn.on_value_written(
    reference
=r"/user/{uid=*@gmail.com}",
    instance
="my-app-db-*",
   
# This example assumes us-central1, but to set location:
   
# region="europe-west1",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
   
# ...
   
pass

Estos parámetros dirigen su función para manejar escrituras en una ruta determinada dentro de la instancia de Realtime Database.

Las especificaciones de ruta coinciden con todas las escrituras que tocan una ruta, incluidas las escrituras que ocurren en cualquier lugar debajo de ella. Si configura la ruta para su función como /foo/bar , coincidirá con eventos en ambas ubicaciones:

 /foo/bar
 
/foo/bar/baz/really/deep/path

En cualquier caso, Firebase interpreta que el evento ocurre en /foo/bar y los datos del evento incluyen los datos antiguos y nuevos en /foo/bar . Si los datos del evento pueden ser grandes, considere usar múltiples funciones en rutas más profundas en lugar de una sola función cerca de la raíz de su base de datos. Para obtener el mejor rendimiento, solicite únicamente datos al nivel más profundo posible.

Comodines y captura

Puede utilizar {key} , {key=*} , {key=prefix*} , {key=*suffix} para capturar. * , prefix* , *suffix para comodines de un solo segmento. Nota: ** representa comodines de múltiples segmentos, que Realtime Database no admite. Consulte Comprender los patrones de ruta .

Comodín de ruta. Puede especificar un componente de ruta como comodín:

  • Usando asterisco, * . Por ejemplo, foo/* coincide con cualquier hijo que se encuentre en un nivel de la jerarquía de nodos por debajo de foo/ .
  • Usando un segmento que contiene exactamente un asterisco, * . Por ejemplo, foo/app*-us coincide con cualquier segmento secundario debajo de foo/ con el prefijo app y el sufijo -us .

Las rutas con comodines pueden coincidir con varios eventos de, por ejemplo, una sola escritura. un inserto de

{
 
"foo": {
   
"hello": "world",
   
"firebase": "functions"
 
}
}

coincide con la ruta "/foo/*" dos veces: una con "hello": "world" y otra con "firebase": "functions" .

Captura de ruta. Puede capturar coincidencias de rutas en variables con nombre para usarlas en su código de función (por ejemplo /user/{uid} , /user/{uid=*-us} ).

Los valores de las variables de captura están disponibles dentro del objeto Database.DatabaseEvent.params de su función.

Comodín de instancia. También puede especificar un componente de instancia utilizando comodines. Un comodín de instancia puede tener prefijo, sufijo o ambos (por ejemplo, my-app-*-prod ).

Comodín y referencia de captura

Con Cloud Functions (segunda generación) y Realtime Database, se puede usar un patrón al especificar ref e instance . Cada interfaz de activación tendrá las siguientes opciones para determinar el alcance de una función:

Especificación de ref Especificar instance Comportamiento
Sencillo ( /foo/bar ) Sin especificar Controlador de ámbitos para todas las instancias en la región de función.
Sencillo ( /foo/bar ) Soltero ( 'my-new-db' ) Controlador de ámbitos para la instancia específica en la región de función.
Sencillo ( /foo/bar ) Patrón ( 'inst-prefix*' ) Controlador de ámbitos para todas las instancias que coinciden con el patrón en la región de función.
Patrón ( /foo/{bar} ) Sin especificar Controlador de ámbitos para todas las instancias en la región de función.
Patrón ( /foo/{bar} ) Soltero ( 'my-new-db' ) Controlador de ámbitos para la instancia específica en la región de función.
Patrón ( /foo/{bar} ) Patrón ( 'inst-prefix*' ) Controlador de ámbitos para todas las instancias que coinciden con el patrón en la región de función.

Manejar datos de eventos

Cuando se activa un evento de Realtime Database, pasa un objeto Event a su función de controlador. Este objeto tiene una propiedad data que, para eventos de creación y eliminación, contiene una instantánea de los datos creados o eliminados.

En este ejemplo, la función recupera los datos de la ruta de referencia, convierte la cadena en esa ubicación a mayúsculas y escribe esa cadena modificada en la base de datos:

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
// for all databases in 'us-central1'
exports
.makeuppercase = onValueCreated(
   
"/messages/{pushId}/original",
   
(event) => {
   
// Grab the current value of what was written to the Realtime Database.
     
const original = event.data.val();
      logger
.log("Uppercasing", event.params.pushId, original);
     
const uppercase = original.toUpperCase();
     
// You must return a Promise when performing
     
// asynchronous tasks inside a function, such as
     
// writing to the Firebase Realtime Database.
     
// Setting an "uppercase" sibling in the
     
// Realtime Database returns a Promise.
     
return event.data.ref.parent.child("uppercase").set(uppercase);
   
},
);
@db_fn.on_value_created(reference="/messages/{pushId}/original")
def makeuppercase(event: db_fn.Event[Any]) -> None:
   
"""Listens for new messages added to /messages/{pushId}/original and
    creates an uppercase version of the message to /messages/{pushId}/uppercase
    """


   
# Grab the value that was written to the Realtime Database.
    original
= event.data
   
if not isinstance(original, str):
       
print(f"Not a string: {event.reference}")
       
return

   
# Use the Admin SDK to set an "uppercase" sibling.
   
print(f"Uppercasing {event.params['pushId']}: {original}")
    upper
= original.upper()
    parent
= db.reference(event.reference).parent
   
if parent is None:
       
print("Message can't be root node.")
       
return
    parent
.child("uppercase").set(upper)

Leyendo el valor anterior

Para eventos write o update , la propiedad data es un objeto Change que contiene dos instantáneas que representan el estado de los datos antes y después del evento desencadenante. El objeto Change tiene una propiedad before que le permite inspeccionar lo que se guardó en Realtime Database antes del evento y una propiedad after que representa el estado de los datos después de que ocurrió el evento.

Por ejemplo, la propiedad before se puede usar para garantizar que la función solo escriba texto en mayúsculas cuando se crea por primera vez:

  exports makeUppercase = onValueWritten("/messages/{pushId}/original", (event) => {
       
// Only edit data when it is first created.
       
if (event.data.before.exists()) {
         
return null;
       
}
       
// Exit when the data is deleted.
       
if (!event.data.after.exists()) {
         
return null;
       
}
       
// Grab the current value of what was written to the Realtime Database.
       
const original = event.data.after.val();
        console
.log('Uppercasing', event.params.pushId, original);
       
const uppercase = original.toUpperCase();
       
// You must return a Promise when performing asynchronous tasks inside a Functions such as
       
// writing to the Firebase Realtime Database.
       
// Setting an "uppercase" sibling in the Realtime Database returns a Promise.
       
return event.data.after.ref.parent.child('uppercase').set(uppercase);
     
});
@db_fn.on_value_written(reference="/messages/{pushId}/original")
def makeuppercase2(event: db_fn.Event[db_fn.Change]) -> None:
   
"""Listens for new messages added to /messages/{pushId}/original and
    creates an uppercase version of the message to /messages/{pushId}/uppercase
    """


   
# Only edit data when it is first created.
   
if event.data.before is not None:
       
return

   
# Exit when the data is deleted.
   
if event.data.after is None:
       
return

   
# Grab the value that was written to the Realtime Database.
    original
= event.data.after
   
if not hasattr(original, "upper"):
       
print(f"Not a string: {event.reference}")
       
return

   
# Use the Admin SDK to set an "uppercase" sibling.
   
print(f"Uppercasing {event.params['pushId']}: {original}")
    upper
= original.upper()
    parent
= db.reference(event.reference).parent
   
if parent is None:
       
print("Message can't be root node.")
       
return
    parent
.child("uppercase").set(upper)