Ce guide présente certains des concepts clés de l'architecture des données et les bonnes pratiques pour structurer les données JSON dans votre Firebase Realtime Database.
Créer une base de données correctement structurée nécessite une certaine réflexion. Plus important encore, vous devez planifier la manière dont les données seront enregistrées et récupérées ultérieurement pour simplifier ce processus.
Structure des données: il s'agit d'une arborescence JSON
Toutes les données de Firebase Realtime Database sont stockées en tant qu'objets JSON. Vous pouvez considérer la base de données comme une arborescence JSON hébergée dans le cloud. Contrairement à une base de données SQL, il n'y a ni tables, ni enregistrements. Lorsque vous ajoutez des données à l'arborescence JSON, elles prennent la forme d'un nouveau nœud et d'une clé associée dans la structure JSON existante. Vous pouvez fournir vos propres clés,
comme les ID utilisateur ou les noms sémantiques, ou vous pouvez les fournir
push()
Si vous créez vos propres clés, elles doivent être encodées au format UTF-8 et peuvent être
de 768 octets, et ne peut pas contenir .
, $
, #
, [
, ]
, /
ni la commande ASCII
caractères 0-31 ou 127. Vous ne pouvez pas utiliser de caractères de contrôle ASCII dans les valeurs
eux-mêmes.
Prenons l'exemple d'une application de chat qui permet aux utilisateurs de stocker un profil de base et une liste de contacts. Un profil utilisateur type se trouve dans un chemin d'accès, par exemple
/users/$uid
L'utilisateur alovelace
peut avoir une entrée de base de données qui
ressemble à ceci:
{
"users": {
"alovelace": {
"name": "Ada Lovelace",
"contacts": { "ghopper": true },
},
"ghopper": { ... },
"eclarke": { ... }
}
}
Bien que la base de données utilise une arborescence JSON, les données stockées dans la base de données peuvent être représentés par certains types natifs correspondant aux types JSON disponibles pour vous aider à écrire un code plus facile à gérer.
Bonnes pratiques pour la structure des données
Éviter d'imbriquer des données
Étant donné que Firebase Realtime Database permet d'imbriquer des données jusqu'à 32 niveaux, vous pourriez être tenté de penser qu'il s'agit de la structure par défaut. Toutefois, lorsque vous récupérez des données à un emplacement de votre base de données, vous récupérez également tous ses nœuds enfants. De plus, lorsque vous accordez à un utilisateur un accès en lecture ou en écriture à un nœud de votre base de données, vous lui accordez également l'accès à toutes les données sous ce nœud. En pratique, il est donc préférable de conserver une structure de données plate que possible.
Pour comprendre pourquoi les données imbriquées sont mauvaises, considérez la structure multi-imbriquée suivante :
{
// This is a poorly nested data architecture, because iterating the children
// of the "chats" node to get a list of conversation titles requires
// potentially downloading hundreds of megabytes of messages
"chats": {
"one": {
"title": "Historical Tech Pioneers",
"messages": {
"m1": { "sender": "ghopper", "message": "Relay malfunction found. Cause: moth." },
"m2": { ... },
// a very long list of messages
}
},
"two": { ... }
}
}
Avec cette conception imbriquée, l'itération des données devient problématique. Pour
Par exemple, pour répertorier les titres des conversations par chat, l'intégralité de chats
est requise.
comprenant tous les membres et les messages, à télécharger sur le client.
Aplatir les structures de données
Si les données sont divisées en chemins séparés, aussi appelés dénormalisation, il peut être efficacement téléchargé dans des appels distincts, si nécessaire. Envisagez d'utiliser cette structure aplatie:
{
// Chats contains only meta info about each conversation
// stored under the chats's unique ID
"chats": {
"one": {
"title": "Historical Tech Pioneers",
"lastMessage": "ghopper: Relay malfunction found. Cause: moth.",
"timestamp": 1459361875666
},
"two": { ... },
"three": { ... }
},
// Conversation members are easily accessible
// and stored by chat conversation ID
"members": {
// we'll talk about indices like this below
"one": {
"ghopper": true,
"alovelace": true,
"eclarke": true
},
"two": { ... },
"three": { ... }
},
// Messages are separate from data we may want to iterate quickly
// but still easily paginated and queried, and organized by chat
// conversation ID
"messages": {
"one": {
"m1": {
"name": "eclarke",
"message": "The relay seems to be malfunctioning.",
"timestamp": 1459361875337
},
"m2": { ... },
"m3": { ... }
},
"two": { ... },
"three": { ... }
}
}
Il est désormais possible d'itérer dans la liste des salons en ne téléchargeant que quelques octets par conversation, en extrayant rapidement les métadonnées pour lister ou afficher les salons dans une UI. Les messages peuvent être récupérés séparément et affichés à mesure qu'ils arrivent, ce qui permet à l'interface utilisateur de rester réactive et rapide.
Créer des données évolutives
Lorsque vous créez des applications, il est souvent préférable de télécharger une partie d'une liste. Cela est particulièrement courant si la liste contient des milliers d'enregistrements. Lorsque cette relation est statique et unidirectionnelle, il vous suffit d'imbriquer les objets enfants sous le parent.
Parfois, cette relation est plus dynamique, ou il peut être nécessaire de et dénormaliser ces données. Souvent, vous pouvez dénormaliser les données en utilisant une requête pour récupérer un sous-ensemble des données, Trier et filtrer des données :
Mais cela peut même s'avérer insuffisant. Prenons l'exemple d'une relation bidirectionnelle entre les utilisateurs et les groupes. Les utilisateurs peuvent appartenir à un groupe, qui comprend liste d'utilisateurs. Au moment de décider à quels groupes appartient un utilisateur, les choses se compliquent.
Il est nécessaire de trouver un moyen élégant de lister les groupes auxquels un utilisateur appartient et de ne récupérer que les données de ces groupes. L'index des groupes est très utile ici:
// An index to track Ada's memberships
{
"users": {
"alovelace": {
"name": "Ada Lovelace",
// Index Ada's groups in her profile
"groups": {
// the value here doesn't matter, just that the key exists
"techpioneers": true,
"womentechmakers": true
}
},
...
},
"groups": {
"techpioneers": {
"name": "Historical Tech Pioneers",
"members": {
"alovelace": true,
"ghopper": true,
"eclarke": true
}
},
...
}
}
Vous remarquerez peut-être que cela duplique
certaines données en stockant la relation
sous le dossier d'Ada et dans le groupe. alovelace
est désormais indexé dans un groupe, et techpioneers
est listé dans le profil d'Ada. Pour supprimer Ada,
du groupe, il doit être mis à
jour à deux endroits.
Il s'agit d'une redondance nécessaire pour les relations bidirectionnelles. Il vous permet d'extraire rapidement et efficacement les adhésions d'Ada, même lorsque la liste des utilisateurs ou des groupes atteint des millions d'éléments ou lorsque les règles de sécurité de la base de données en temps réel empêchent l'accès à certains enregistrements.
Cette approche, qui inverse les données en listant les ID en tant que clés et en définissant la valeur sur "true", permet de vérifier une clé aussi simplement que de lire /users/$uid/groups/$group_id
et de vérifier s'il s'agit de null
. L'index est plus rapide
et bien plus efficace que d'interroger
ou d'analyser les données.