query leer from datos data consultas child addlistenerforsinglevalueevent javascript firebase web firebase-database firebase-realtime-database nosql

javascript - leer - Cómo escribir datos desnormalizados en Firebase



leer datos firebase android (2)

He leído los documentos de Firebase sobre Stucturing Data . El almacenamiento de datos es barato, pero el tiempo del usuario no lo es. Deberíamos optimizar para obtener operaciones y escribir en múltiples lugares.

Entonces, podría almacenar un nodo de lista y un nodo de índice de lista , con algunos datos duplicados entre los dos, al menos el nombre de la lista.

Estoy usando ES6 y promete en mi aplicación javascript para manejar el flujo asíncrono, principalmente de buscar una clave de referencia de firebase después del primer envío de datos.

let addIndexPromise = new Promise( (resolve, reject) => { let newRef = ref.child(''list-index'').push(newItem); resolve( newRef.key()); // ignore reject() for brevity }); addIndexPromise.then( key => { ref.child(''list'').child(key).set(newItem); });

¿Cómo me aseguro de que los datos permanezcan sincronizados en todos los lugares , sabiendo que mi aplicación solo se ejecuta en el cliente?

Para verificar la cordura, configuré un setTimeout en mi promesa y cerré mi navegador antes de que se resolviera, y de hecho mi base de datos ya no era consistente, con un índice adicional guardado sin una lista correspondiente .

¿Algún consejo?


Gran pregunta Sé de tres enfoques para esto, que enumeraré a continuación.

Tomaré un ejemplo ligeramente diferente para esto, principalmente porque me permite usar términos más concretos en la explicación.

Digamos que tenemos una aplicación de chat, donde almacenamos dos entidades: mensajes y usuarios. En la pantalla donde mostramos los mensajes, también mostramos el nombre del usuario. Entonces, para minimizar el número de lecturas, también almacenamos el nombre del usuario con cada mensaje de chat.

users so:209103 name: "Frank van Puffelen" location: "San Francisco, CA" questionCount: 12 so:3648524 name: "legolandbridge" location: "London, Prague, Barcelona" questionCount: 4 messages -Jabhsay3487 message: "How to write denormalized data in Firebase" user: so:3648524 username: "legolandbridge" -Jabhsay3591 message: "Great question." user: so:209103 username: "Frank van Puffelen" -Jabhsay3595 message: "I know of three approaches, which I''ll list below." user: so:209103 username: "Frank van Puffelen"

Por lo tanto, almacenamos la copia primaria del perfil del usuario en el nodo de users . En el mensaje almacenamos el uid (por lo tanto: 209103 y por lo tanto: 3648524) para que podamos buscar al usuario. Pero también almacenamos el nombre del usuario en los mensajes, para que no tengamos que buscar esto para cada usuario cuando queremos mostrar una lista de mensajes.

Entonces, ¿qué sucede cuando voy a la página Perfil en el servicio de chat y cambio mi nombre de "Frank van Puffelen" a solo "puf"?

Actualización transaccional

Realizar una actualización transaccional es la que probablemente les viene a la mente a la mayoría de los desarrolladores inicialmente. Siempre queremos que el username de username en los mensajes coincida con el name en el perfil correspondiente.

Uso de escrituras de múltiples rutas (agregado en 20150925)

Desde Firebase 2.3 (para JavaScript) y 2.4 (para Android e iOS), puede lograr actualizaciones atómicas con bastante facilidad utilizando una única actualización de múltiples rutas:

function renameUser(ref, uid, name) { var updates = {}; // all paths to be updated and their new values updates[''users/''+uid+''/name''] = name; var query = ref.child(''messages'').orderByChild(''user'').equalTo(uid); query.once(''value'', function(snapshot) { snapshot.forEach(function(messageSnapshot) { updates[''messages/''+messageSnapshot.key()+''/username''] = name; }) ref.update(updates); }); }

Esto enviará un solo comando de actualización a Firebase que actualiza el nombre del usuario en su perfil y en cada mensaje.

Enfoque atómico previo

Entonces, cuando el usuario cambia el name en su perfil:

var ref = new Firebase(''https://mychat.firebaseio.com/''); var uid = "so:209103"; var nameInProfileRef = ref.child(''users'').child(uid).child(''name''); nameInProfileRef.transaction(function(currentName) { return "puf"; }, function(error, committed, snapshot) { if (error) { console.log(''Transaction failed abnormally!'', error); } else if (!committed) { console.log(''Transaction aborted by our code.''); } else { console.log(''Name updated in profile, now update it in the messages''); var query = ref.child(''messages'').orderByChild(''user'').equalTo(uid); query.on(''child_added'', function(messageSnapshot) { messageSnapshot.ref().update({ username: "puf" }); }); } console.log("Wilma''s data: ", snapshot.val()); }, false /* don''t apply the change locally */);

Bastante involucrado y el lector astuto notará que hago trampa en el manejo de los mensajes. El primer truco es que nunca llamo al oyente, pero tampoco uso una transacción.

Si queremos hacer este tipo de operación de forma segura desde el cliente, necesitaríamos:

  1. reglas de seguridad que aseguran que coincidan los nombres en ambos lugares. Pero las reglas deben permitir suficiente flexibilidad para que sean temporalmente diferentes mientras cambiamos el nombre. Entonces esto se convierte en un esquema de compromiso de dos fases bastante doloroso.
    1. cambie todos los campos de username para mensajes de so:209103 a null (algún valor mágico)
    2. cambie el name del usuario so:209103 a ''puf''
    3. cambie el username de username en cada mensaje so:209103 que es null para puf .
    4. esa consulta requiere una and de dos condiciones, que las consultas de Firebase no admiten. Así que terminaremos con una propiedad adicional uid_plus_name (con valor so:209103_puf ) sobre la que podemos consultar.
  2. Código del lado del cliente que maneja todas estas transiciones transaccionalmente.

Este tipo de enfoque me duele la cabeza. Y generalmente eso significa que estoy haciendo algo mal. Pero incluso si es el enfoque correcto, con una cabeza que duele, es mucho más probable que cometa errores de codificación. Así que prefiero buscar una solución más simple.

Consistencia eventual

Actualización (20150925) : Firebase lanzó una función para permitir escrituras atómicas en múltiples rutas. Esto funciona de manera similar al enfoque a continuación, pero con un solo comando. Consulte la sección actualizada más arriba para leer cómo funciona esto.

El segundo enfoque depende de dividir la acción del usuario ("Quiero cambiar mi nombre a ''puf''") de las implicaciones de esa acción ("Necesitamos actualizar el nombre en el perfil así: 209103 y en cada mensaje que tiene user = so:209103 ).

Manejaría el cambio de nombre en un script que ejecutamos en un servidor. El método principal sería algo como esto:

function renameUser(ref, uid, name) { ref.child(''users'').child(uid).update({ name: name }); var query = ref.child(''messages'').orderByChild(''user'').equalTo(uid); query.once(''value'', function(snapshot) { snapshot.forEach(function(messageSnapshot) { messageSnapshot.update({ username: name }); }) }); }

Una vez más, tomo algunos accesos directos aquí, como usar once(''value'' (que en general es una mala idea para un rendimiento óptimo con Firebase). Pero en general, el enfoque es más simple, a costa de no tener todos los datos completamente actualizados en al mismo tiempo, pero eventualmente todos los mensajes se actualizarán para que coincidan con el nuevo valor.

No preocuparse

El tercer enfoque es el más simple de todos: en muchos casos, realmente no tiene que actualizar los datos duplicados. En el ejemplo que hemos usado aquí, se podría decir que cada mensaje registró el nombre como lo usé en ese momento. No cambié mi nombre hasta ahora, por lo que tiene sentido que los mensajes más antiguos muestren el nombre que usé en ese momento. Esto se aplica en muchos casos donde los datos secundarios son de naturaleza transaccional. Por supuesto, no se aplica en todas partes, pero donde se aplica "no importa" es el enfoque más simple de todos.

Resumen

Si bien lo anterior son solo descripciones amplias de cómo podría resolver este problema y definitivamente no están completas, encuentro que cada vez que necesito desplegar datos duplicados, vuelve a uno de estos enfoques básicos.


Para agregar a la gran respuesta de Frank, implementé el enfoque de coherencia eventual con un conjunto de funciones de Firebase Cloud . Las funciones se activan cada vez que se cambia un valor primario (por ejemplo, nombre de usuario) y luego propagan los cambios a los campos desnormalizados.

No es tan rápido como una transacción, pero en muchos casos no es necesario.