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:
-
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.
-
cambie todos los campos de
username
para mensajes deso:209103
anull
(algún valor mágico) -
cambie el
name
del usuarioso:209103
a ''puf'' -
cambie el
username
deusername
en cada mensajeso:209103
que esnull
parapuf
. -
esa consulta requiere una
and
de dos condiciones, que las consultas de Firebase no admiten. Así que terminaremos con una propiedad adicionaluid_plus_name
(con valorso:209103_puf
) sobre la que podemos consultar.
-
cambie todos los campos de
- 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.