UPDATE : Il y a maintenant un doc sur la structuration des données . Voir également cet excellent article sur Structures de données NoSQL .
Le principal problème des données hiérarchiques, par opposition aux SGBDR, est qu'il est tentant d'imbriquer les données parce qu'on le peut. En général, vous souhaitez normaliser les données dans une certaine mesure (tout comme vous le feriez avec SQL) malgré l'absence d'instructions de jointure et de requêtes.
Vous souhaitez également dénormaliser dans les endroits où l'efficacité de la lecture est une préoccupation. C'est une technique utilisée par toutes les applications à grande échelle (par exemple Twitter et Facebook) et bien qu'elle aille à l'encontre de nos principes DRY, c'est généralement une caractéristique nécessaire des applications évolutives.
L'essentiel ici est que vous devez travailler dur sur les écritures pour rendre les lectures faciles. Gardez les composants logiques qui sont lus séparément (par exemple, pour les salons de discussion, ne mettez pas les messages, les méta-informations sur les salons et les listes de membres 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 est 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 sont constamment modifiées, partagées, réconciliées, etc., ce qui nécessite un modèle interne plus simple pour contrôler les clients synchronisés).
Un simple exemple vous mettra probablement dans le bon état d'esprit, alors voilà :
/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 électroniques 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 toutes les données des utilisateurs. messages
y widgets
aussi. Ce n'est pas grave si aucune de ces choses ne se compte en milliers. Mais un gros problème pour 10 000 utilisateurs avec plus de 5 000 messages chacun.
La stratégie optimale pour une structure hiérarchique en temps réel devient donc plus évidente :
/user_meta/uid/email
/messages/uid/...
/widgets/uid/...
Les index sont un outil supplémentaire extrêmement utile dans cet environnement. En créant un index des 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, disons, obtenir les messages des 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 donné quelques détails dans un autre billet de SO sur la dénormalisation des données, alors vérifiez-les aussi . Je vois que Frank a déjà publié l'article d'Anant, je ne vais donc pas le répéter ici, mais c'est aussi une excellente lecture.