Ottima domanda. Conosco tre approcci a questo, che elencherò qui di seguito.
Prenderò un esempio leggermente diverso, soprattutto perché consente di utilizzare termini più concreti nella spiegazione.
Supponiamo di avere un'applicazione di chat, in cui vengono archiviate due entità: messaggi e utenti. Nella schermata in cui mostriamo i messaggi, mostriamo anche il nome dell'utente. Quindi, per ridurre al minimo il numero di letture, memorizziamo il nome dell'utente anche con ogni messaggio di 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"
Così abbiamo memorizzare la copia principale del profilo dell'utente nel nodo users
. Nel messaggio memorizziamo il uid
(così: 209103 e così: 3648524) in modo che possiamo cercare l'utente. Ma noi anche memorizziamo il nome dell'utente nei messaggi, in modo che non dobbiamo cercare questo per ogni utente quando vogliamo visualizzare un elenco di messaggi.
Così ora cosa succede quando vado alla pagina Profilo del servizio chat e cambio il mio nome da "Frank van Puffelen" a solo "puf".
aggiornamento transazionale
Esecuzione di un aggiornamento transazionale è quello che, probabilmente, si apre alla mente della maggior parte degli sviluppatori inizialmente. Vogliamo sempre il username
nei messaggi per corrispondere allo name
nel profilo corrispondente.
Utilizzando multipath scrive (aggiunto il 20.150.925)
Dal Firebase 2.3 (per JavaScript) e 2,4 (per Android e iOS), è possibile ottenere aggiornamenti atomiche abbastanza facilmente utilizzando un singolo aggiornamento multipath:
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);
});
}
Questo invierà un singolo comando aggiornamento per Firebase che aggiorna il nome dell'utente nel proprio profilo e in ogni messaggio.
precedente approccio atomico
Così, quando il cambio utente è il name
nel loro profilo:
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 */);
Abbastanza coinvolti e il lettore attento avrà notato che ho imbrogliato nella gestione dei messaggi. Il primo cheat è che non ho mai chiamato off
per il listener, ma non uso una transazione.
Se vogliamo fare in modo sicuro questo tipo di operazione da parte del cliente, avremmo bisogno:
- regole di sicurezza che garantiscono i nomi in entrambi i luoghi partita. Ma le regole devono consentire sufficiente flessibilità per loro di essere temporaneamente diverse mentre stiamo cambiando il nome. Quindi questo si trasforma in uno schema di commit a due fasi piuttosto doloroso.
- cambiamento tutte
username
i campi per i messaggi di so:209103
a null
(qualche valore magico)
- cambiamento del
name
di utente so:209103
a 'puf'
- cambiamento del
username
in ogni messaggio per so:209103
che è null
a puf
.
- la query richiede uno
and
di due condizioni, che le query Firebase non supportano. Quindi finiremo con una proprietà aggiuntiva uid_plus_name
(con valore so:209103_puf
) su cui possiamo eseguire una query.
- codice lato client che gestisce tutte le transizioni a livello di transazione.
Questo tipo di approccio mi fa male alla testa. E di solito significa che sto facendo qualcosa di sbagliato. Ma anche se è l'approccio giusto, con una testa che mi fa male ho più possibilità di commettere errori di programmazione. Quindi preferisco cercare una soluzione più semplice.
coerenza eventuale
Update (20.150.925): Firebase rilasciato una funzione per consentire scritture atomiche a percorsi multipli. Funziona in modo simile all'approccio di seguito, ma con un singolo comando. Vedere la sezione aggiornata sopra per leggere come funziona.
Il secondo approccio dipende dalla divisione dell'azione dell'utente ("Voglio cambiare il mio nome in" puf "") dalle implicazioni di tale azione ("Abbiamo bisogno di aggiornare il nome nel profilo così: 209103 e in ogni messaggio che ha user = so:209103
)
mi piacerebbe gestire la ridenominazione in uno script che si corre su un server il metodo principale sarebbe qualcosa di simile:..
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 });
})
});
}
ancora una volta mi prendo un paio di scorciatoie qui, come usare once('value'
(che in generale è una cattiva idea per prestazioni ottimali con Firebase). Ma nel complesso l'approccio è più semplice, a Il costo di non avere tutti i dati completamente aggiornati allo stesso tempo. Ma alla fine i messaggi verranno aggiornati per corrispondere al nuovo valore.
fregandosene
Il terzo approccio è il più semplice di tutti: in molti casi non si hanno veramente a aggiornare i dati duplicato a tutti. Nell'esempio che abbiamo usato qui, si potrebbe dire che ogni messaggio ha registrato il nome come l'ho usato in quel momento. Non ho cambiato il mio nome fino ad ora, quindi ha senso che i vecchi messaggi mostrino il nome che ho usato in quel momento. Ciò si applica in molti casi in cui i dati secondari sono di natura transazionale. Ovviamente non si applica ovunque, ma dove si applica "non importa" è l'approccio più semplice di tutti.
Sommario
Mentre il sopra sono solo ampie descrizioni di come si potrebbe risolvere questo problema e non sono assolutamente completa, trovo che ogni volta che ho bisogno di ventaglio dati duplicati si ritorna a uno di questi base approcci.
* Scriverò una risposta corretta di seguito. * Questa prima frase mi rende felice. Puoi ripeterlo alle feste di compleanno? Soprattutto quando sono presenti gli amici dev. :-) –
questo sembra rilevante, anche .. https://stackoverflow.com/questions/47334382 – Fattie