Subscribe to our Newsletter

Receive our news and insights

Blog / Spanish  

Lecciones Aprendidas: Firebase Database

A Picture of Luis Talavera
By:
December 12, 2017 | Topic: Spanish  
Lecciones Aprendidas: Firebase Database

Lessons Learned - Firebase Database

Si estás aquí, probablemente ya conozcas Firebase. Pero de no ser el caso, aquí te ofrecemos una pequeña descripción: Firebase es una plataforma back-endas-a-service, probablemente la más popular en 2017. Ofrece distintos servicios como análisis, autenticación, base de datos casi en tiempo real, notificaciones, y funciones de nube. Además, es compatible con varias plataformas y lenguajes, como Android, iOS, Javascript y Unity, entre otros. Sin embargo, el enfoque de este artículo es la base de datos casi en tiempo real (Firebase database), sus beneficios, los problemas que tuvimos y lo que aprendimos.

Probablemente ya hayas llegado al problema de modelar tu base de datos en Firebase y, déjanos adivinar, no fue tan simple como pensaste que sería. En nuestro caso, pasamos mucho tiempo discutiendo cómo debería estructurarse, pensando en las relaciones y las entidades. El problema aquí es que para nosotros, y probablemente para ti, que venimos de un mundo relacional, todo se trata del esquema y la normalización de datos, mientras que en Firebase tienes que estructurar tus datos después de tu view, y la desnormalización está perfectamente bien.

Durante mucho tiempo, la única opción para el almacenamiento de datos ha sido las bases de datos relacionales, por lo que estamos acostumbrados a pensar de una manera relacional. Sin embargo, estoy seguro de que has escuchado el término NoSQL, bases de datos alternativas (en su mayoría de código abierto) que vienen llamando la atención gracias a su manera de manejar datos para resolver diferentes problemas (por ejemplo, búsqueda en Internet, aplicaciones web a gran escala, redes sociales). Y, como podrás imaginar, la Firebase database es una base de datos NoSQL basada en documentos JSON (árbol JSON).

Vale la pena utilizar las bases de datos NoSQL para situaciones que requieren una interacción de datos diferente o exigen el uso de un cluster. Además, se comportan de manera diferente a las bases de datos relacionales. Por ejemplo, las propiedades de ACID suelen chocar con su entorno (es decir, clusters) y funcionan sin un esquema, por lo que debemos comprender la naturaleza de los datos y cómo queremos manipularlos antes del modelado. Pero permítanme detenerme aquí, los lectores interesados ​​pueden encontrar más información relacionada con el mundo NoSQL en la referencia al final de este artículo.

Ahora repasemos algunas buenas prácticas para la Firebase database.

Cómo estructurar tus datos

Los datos desnormalizados son algo muy común en Firebase, y también es algo a lo que no estábamos muy acostumbrados. En base a nuestra experiencia con bases de datos relacionales, aprendimos que debemos evitar duplicar datos, pero en el caso de Firebase es algo que nos permite ahorrar mucho tiempo al reducir las consultas. Por ejemplo, toma el primer modelo que se nos ocurrió para una aplicación de eventos en el Listado 1, donde intentamos modelar la base de datos con un pensamiento relacional.

Listado 1: Nuestro primer modelo de base de datos

{
    "eventInformation": {
        "bannerImageUrl" : "http://images..../MC-2018.jpg",
        "title" : "Mobile Congress 2018",
        "date" : 1501502400
    },  
    "users": {
        "TPjbF78fKmdvRIC3Awzo03ygm692" : {
            "displayName" : "Juan Valencia",
            "email" : "jvalencia@belatrixsf.com",
            "phoneNumber" : "956068715",
            "photoUrl" : "https://.../s96-c/photo.jpg",
        },
        ...
    }
    "sessions": {
        "77Sg79oDbJSfVTC8kPWpjywi7263" : {
            "capacity" : 100,
            "name" : "Introduction to Firebase",
            "registrations" : {
                "TPjbF78fKmdvRIC3Awzo03ygm692" : true,
                "2xJxDomSYAMZO82nMu2egf0Vh0Z2": true
            }
        }
    }
}

Hay un par de problemas con este enfoque. En primer lugar, cada vez que queremos recuperar la información de una sesión, también obtendremos todos los registros (imagina más de 100 registros) porque están anidados (Deep Data es un antipatrón en Firebase). En segundo lugar, colocamos todas las claves de los usuarios (Las claves pueden crearse manualmente, pero generalmente las genera la base de datos, por ejemplo, 77Sg79oDbJSfVTC8kPWpjywi7263) en la matriz de registros para evitar la duplicación de datos, lo que nos obligará a realizar más consultas para obtener la información de cada usuario registrado (es decir, uniones).

Listado 2: Estructura poco profunda

{
    "registrations" : {
        "77Sg79oDbJSfVTC8kPWpjywi7263" : { 
            "TPjbF78fKmdvRIC3Awzo03ygm692" : {
                "displayName" : "Juan Valencia",
                "email" : "jvalencia@belatrixsf.com",
                "phoneNumber" : "956068715",
                "photoUrl" : "https://.../s96-c/photo.jpg",
            },
            "2xJxDomSYAMZO82nMu2egf0Vh0Z2" : {
                "displayName" : "Alberto Garcia",
                "email" : "agarcia@belatrixsf.com",
                "phoneNumber" : "956765726",
                "photoUrl" : "https://.../s86-c/photo.jpg",
            }
        }
    }
}

Para solucionarlo, debemos eliminar los registros del nodo de sesiones y establecerlos como un nuevo nodo independiente. Eso nos permitirá evitar la recuperación de datos innecesarios cuando consultemos los detalles de cada sesión, y nos permitirá mantener una estructura poco profunda. Además, si queremos mostrar la información de cada usuario registrado, podemos evitar varias consultas de unión al duplicar los datos de los usuarios en cada nodo de registro (consulta el Listado 2). Los datos duplicados pueden mejorar el rendimiento al reducir la cantidad de consultas complejas. Sin embargo, eso significa que varias ubicaciones requerirán actualizaciones simultáneas.

Al final, se reduce a la forma en que una aplicación va a mostrar los datos, y mantiene el equilibrio entre los datos duplicados y la consistencia.

Manteniendo consistencia

Cada vez que modificas tus datos duplicados, éstos pueden dañarse por un acceso concurrente o por un error en una de las operaciones. Para mantener la consistencia, Firebase ofrece dos funciones: transacciones y actualizaciones de múltiples rutas. La primera solo puede operar en un nodo raíz a la vez (por ejemplo, sesiones o usuarios en la base de datos de ejemplo), y si al menos una de las operaciones es rechazada, se ejecutará nuevamente (intentos múltiples). La última comprime varias operaciones en una operación atómica (todo o nada), y permite operar en diferentes nodos raíz a la vez (por ejemplo, usuarios, sesiones, etc.).

Listado 3: Transacción sobre la referencia de registro

registrationsRef.runTransaction(new Transaction.Handler() {
        @Override
        public Transaction.Result doTransaction(MutableData mutableData) {
            // Here goes your logic over mutableData (registrations reference)
            return Transaction.success(mutableData);
        }

        @Override
        public void onComplete(DatabaseError e, boolean b, DataSnapshot snapshot) {
            // Transaction completed
        }
});

El Listado 3 muestra el esqueleto básico de una transacción. Una ventaja de las transacciones sobre las actualizaciones de múltiples rutas es que obtiene los valores actuales de la base de datos en la doTransaction callback. Pero, como se mencionó anteriormente, no funcionará en diferentes nodos, lo que significa que si deseas actualizar el nodo de sesiones, no puedes usar la misma transacción para actualizar los usuarios o los nodos de eventInformation al mismo tiempo. De manera diferente, el código en el Listado 4 muestra una actualización de múltiples rutas, ejecutándose en nuestra base de datos de ejemplo, que está modificando dos nodos raíz “/ usuarios” y “/ registros” al mismo tiempo.

Listado 4: Actualización de múltiples rutas

Map<String, Object> childUpdates = new HashMap<>();
childUpdates.put("/users/TPjbF78fKmdvRIC3Awzo03ygm692", null);
childUpdates.put("/registrations/.../TPjbF78fKmdvRIC3Awzo03ygm692", null);
databaseRef.updateChildren(childUpdates);

Un consejo adicional

Con Firebase, la mayor parte de la lógica de la aplicación está en el cliente. Sin embargo, en algún momento es posible que quieras mover una parte del código al back-end. Esto podría deberse a la duplicación de la lógica en los clientes o al traslado de tareas intensivas a la nube, etc. En este contexto, Firebase Cloud Functions (que todavía está en Beta) te permite ejecutar automáticamente el código de back-end en respuesta a ciertos eventos desencadenados por las diferentes funciones de Firebase y solicitudes HTTPS.

Imagina la siguiente situación en la aplicación de eventos: queremos recuperar la cantidad de registros para cada sesión para evitar que se eleven por encima de cierta capacidad. Mientras que en las bases de datos relacionales probablemente ejecutaremos una consulta select count(*) y compararemos el valor con la capacidad, en Firebase podemos evitar esa consulta y obtener el valor de inmediato. Para que esto suceda tenemos que actualizar un valor de recuento cada vez que un nuevo usuario se registra/anula el registro en/desde una sesión. Ese valor de conteo será un nuevo atributo en cada sesión.

Listado 5: Función de conteo de registros de la nube

exports.countRegistrations = functions.database.ref("/registrations/{sid}/{uid}").onWrite(event => {
  var sid = event.params.sid;
  var countRef = admin.database().ref(`/sessions/${sid}/count'`);

  return countRef.transaction(function(current) {
    if (event.data.exists() && !event.data.previous.exists()) {
      return (current || 0) + 1;
    }
    else if (!event.data.exists() && event.data.previous.exists()) {
      return (current || 0) - 1;
    }
  }).then(() => {
    console.log("Counter updated.");
  });
});

Usando funciones en la nube podemos definir un receptor en el back-end que actualizará el valor de conteo cada vez que se actualice el nodo de registros, consulta el código en el Listado 5. Usamos la onWrite callback para escuchar crear / actualizar / eliminar eventos y comenzar una acción inmediatamente después. Las condiciones de transacción garantizan que las modificaciones (eventos de actualización) no cambien el valor del atributo de conteo.

La cadena “/ registrations / sid / uid” define la ruta para el receptor de eventos, donde los campos sid uid entre llaves actúan como comodines. Por lo tanto, nuestra función de nube countRegistrations recepcionará todas las modificaciones de la base de datos en dicha ruta. Después de eso, obtendrá el sid que es la clave de sesión (el uid es la clave del usuario) y llamará una transacción para modificar el atributo del conteo de dicha sesión (devuelve una promesa). Finalmente, registrará un mensaje “Conteo actualizado”.

Conclusiones

La Firebase database o las bases de datos casi en tiempo real funcionan muy bien; sin embargo, no son completamente gratuitas y existen ciertas limitaciones que debes tener en cuenta. Además, debes pensar en cómo vas a mostrar tus datos antes de crear una estructura para tu base de datos. Solo piensa que la estructura de la base de datos debe coincidir con tus views. Por otro lado, tienes que evitar las estructuras profundas (anidadas) y equilibrar los datos duplicados con consistencia. Finalmente, aunque puedes implementar gran parte de tu lógica en cada cliente (por ejemplo, Android, iOS), a veces es mejor implementarla como una función en la nube para evitar la repetición del código o para eliminar un procedimiento intenso de una aplicación móvil.

Nota

Encontramos un problema al usar las transacciones del cliente en Android. Cuando eliminamos un valor de la base de datos utilizando transacciones, si hay una función en la nube que recibe dicha ruta (ya sea onWrite u onDelete), ésta no se llamará. Eso no ocurre si utilizamos actualizaciones de múltiples rutas. Esto puede ser un error que podría corregirse en futuras versiones ya que, como mencionamos anteriormente, las funciones en la nube todavía están en fase beta.

Referencias

[1] NoSQL Distilled: A Brief Guide to the Emerging World of Polyglot Persistence, Sadalage, Pramod J. and Fowler, Martin, isbn: 0321826620

Contenido relacionado

¿Por qué DevOps?

The chatbot revolution

Related Services

EXECUTIVE INSIGHTS

Financial  

The changing face of payments, security, and AI in finance

By

November 20 / 2019

1 Stars2 Stars3 Stars4 Stars5 Stars
Loading...

Money 20/20 – Belatrix’s key takeaways on payments, security and AI A couple of weeks ago, Belatrix, now a Globant Division, sponsored one of the largest global financial services...

Read post

HOT
TOPIC