MISE À JOUR : Il existe maintenant un document sur la structuration des données . Consultez également cet excellent article sur les structures de données NoSQL .
Le principal problème avec les données hiérarchiques, par opposition au SGBDR, est qu'il est tentant d'imbriquer des données parce que nous le pouvons. En règle générale, vous souhaitez normaliser les données dans une certaine mesure (comme vous le feriez avec SQL) malgré le manque d'instructions et de requêtes de jointure.
Vous souhaitez également dénormaliser dans les endroits où l'efficacité de lecture est un problème. Il s'agit d'une technique utilisée par toutes les applications à grande échelle (par exemple Twitter et Facebook) et bien qu'elle soit contraire à nos principes DRY, c'est généralement une caractéristique nécessaire des applications évolutives.
L'essentiel ici est que vous voulez travailler dur sur les écritures pour faciliter les lectures. Gardez les composants logiques qui sont lus séparément séparément (par exemple pour les salles de discussion, ne mettez pas les messages, les méta-informations sur les salles et les listes de membres tous au même endroit, si vous voulez pouvoir itérer les groupes plus tard).
La principale différence entre les données en temps réel de Firebase et un environnement SQL réside dans l'interrogation des données. Il n'y a pas de moyen simple de dire "SELECT USERS WHERE X = Y", en raison de la nature en temps réel des données (elles changent constamment, partitionnent, réconcilient, etc., ce qui nécessite un modèle interne plus simple pour garder les clients synchronisés sous contrôle)
Un exemple simple vous mettra probablement dans le bon état d'esprit, alors voici:
/users/uid
/users/uid/email
/users/uid/messages
/users/uid/widgets
Maintenant, puisque nous sommes dans une structure hiérarchique, si je veux itérer les adresses e-mail des utilisateurs, je fais quelque chose comme ceci:
// I could also use on('child_added') here to great success
// but this is simpler for an example
firebaseRef.child('users').once('value')
.then(userPathSnapshot => {
userPathSnapshot.forEach(
userSnap => console.log('email', userSnap.val().email)
);
})
.catch(e => console.error(e));
Le problème avec cette approche est que je viens de forcer le client à télécharger tous les utilisateurs messages
et widgets
aussi. Pas de problème si aucune de ces choses ne se chiffre en milliers. Mais un gros problème pour 10 000 utilisateurs avec plus de 5 000 messages chacun.
Alors maintenant, la stratégie optimale pour une structure hiérarchique en temps réel devient plus évidente:
/user_meta/uid/email
/messages/uid/...
/widgets/uid/...
Les indices sont un outil supplémentaire extrêmement utile dans cet environnement. En créant un index d'utilisateurs avec certains attributs, je peux rapidement simuler une requête SQL en itérant simplement l'index:
/users_with_gmail_accounts/uid/email
Maintenant, si je veux, par exemple, recevoir des messages pour les utilisateurs de Gmail, je peux faire quelque chose comme ceci:
var ref = firebase.database().ref('users_with_gmail_accounts');
ref.once('value').then(idx_snap => {
idx_snap.forEach(idx_entry => {
let msg = idx_entry.name() + ' has a new message!';
firebase.database().ref('messages').child(idx_entry.name())
.on(
'child_added',
ss => console.log(msg, ss.key);
);
});
})
.catch(e => console.error(e));
J'ai offert quelques détails dans un autre article du SO sur la dénormalisation des données, alors vérifiez-les également . Je vois que Frank a déjà publié l'article d'Anant, donc je ne le répéterai pas ici, mais c'est aussi une excellente lecture.