Questa guida copre alcuni dei concetti chiave nell'architettura dei dati e le best practice per strutturare i dati JSON nel tuo database Firebase Realtime.
Costruire un database adeguatamente strutturato richiede un po' di accortezza. Ancora più importante, è necessario pianificare il modo in cui i dati verranno salvati e successivamente recuperati per rendere il processo il più semplice possibile.
Come sono strutturati i dati: è un albero JSON
Tutti i dati del database Firebase Realtime vengono archiviati come oggetti JSON. Puoi pensare al database come a un albero JSON ospitato nel cloud. A differenza di un database SQL, non ci sono tabelle o record. Quando aggiungi dati all'albero JSON, diventa un nodo nella struttura JSON esistente con una chiave associata. Puoi fornire le tue chiavi, come ID utente o nomi semantici, oppure possono essere fornite per te usando push()
.
Se crei le tue chiavi, devono essere codificate UTF-8, possono avere un massimo di 768 byte e non possono contenere .
, $
, #
, [
, ]
, /
o caratteri di controllo ASCII 0-31 o 127. Non è possibile utilizzare nemmeno i caratteri di controllo ASCII nei valori stessi.
Si consideri, ad esempio, un'applicazione di chat che consente agli utenti di memorizzare un profilo di base e un elenco di contatti. Un tipico profilo utente si trova in un percorso, come /users/$uid
. L'utente alovelace
potrebbe avere una voce di database simile a questa:
{
"users": {
"alovelace": {
"name": "Ada Lovelace",
"contacts": { "ghopper": true },
},
"ghopper": { ... },
"eclarke": { ... }
}
}
Sebbene il database utilizzi un albero JSON, i dati archiviati nel database possono essere rappresentati come determinati tipi nativi che corrispondono ai tipi JSON disponibili per aiutarti a scrivere codice più gestibile.
Migliori pratiche per la struttura dei dati
Evita di annidare i dati
Poiché Firebase Realtime Database consente di annidare dati fino a 32 livelli di profondità, potresti essere tentato di pensare che questa dovrebbe essere la struttura predefinita. Tuttavia, quando si recuperano i dati in una posizione nel database, si recuperano anche tutti i relativi nodi figlio. Inoltre, quando concedi a qualcuno l'accesso in lettura o scrittura a un nodo nel tuo database, concedi loro anche l'accesso a tutti i dati in quel nodo. Pertanto, in pratica, è meglio mantenere la struttura dei dati il più piatta possibile.
Per un esempio del motivo per cui i dati nidificati non sono validi, considera la seguente struttura a nidificazione multipla:
{
// 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": { ... }
}
}
Con questo design annidato, l'iterazione dei dati diventa problematica. Ad esempio, l'elenco dei titoli delle conversazioni chat richiede che l'intero albero delle chats
, inclusi tutti i membri e i messaggi, venga scaricato sul client.
Appiattire le strutture dati
Se invece i dati vengono suddivisi in percorsi separati, detti anche denormalizzazione, possono essere scaricati in modo efficiente in chiamate separate, in base alle esigenze. Considera questa struttura appiattita:
{
// 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": { ... }
}
}
È ora possibile scorrere l'elenco delle stanze virtuali scaricando solo pochi byte per conversazione, recuperando rapidamente i metadati per elencare o visualizzare le stanze in un'interfaccia utente. I messaggi possono essere recuperati separatamente e visualizzati non appena arrivano, consentendo all'interfaccia utente di rimanere reattiva e veloce.
Crea dati scalabili
Quando si creano app, spesso è meglio scaricare un sottoinsieme di un elenco. Ciò è particolarmente comune se l'elenco contiene migliaia di record. Quando questa relazione è statica e unidirezionale, puoi semplicemente annidare gli oggetti figlio sotto il genitore.
A volte, questa relazione è più dinamica o potrebbe essere necessario denormalizzare questi dati. Molte volte puoi denormalizzare i dati usando una query per recuperare un sottoinsieme di dati, come discusso in Ordinamento e filtraggio dei dati .
Ma anche questo potrebbe essere insufficiente. Si consideri, ad esempio, una relazione bidirezionale tra utenti e gruppi. Gli utenti possono appartenere a un gruppo e i gruppi comprendono un elenco di utenti. Quando arriva il momento di decidere a quali gruppi appartiene un utente, le cose si complicano.
Ciò che serve è un modo elegante per elencare i gruppi a cui appartiene un utente e recuperare solo i dati per quei gruppi. Un indice dei gruppi può aiutare molto qui:
// 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
}
},
...
}
}
Potresti notare che questo duplica alcuni dati memorizzando la relazione sia nel record di Ada che nel gruppo. Ora alovelace
è indicizzato in un gruppo e techpioneers
è elencato nel profilo di Ada. Quindi, per eliminare Ada dal gruppo, è necessario aggiornarlo in due punti.
Questa è una ridondanza necessaria per le relazioni bidirezionali. Ti consente di recuperare in modo rapido ed efficiente le iscrizioni ad Ada, anche quando l'elenco di utenti o gruppi raggiunge i milioni o quando le regole di sicurezza del database in tempo reale impediscono l'accesso ad alcuni record.
Questo approccio, invertendo i dati elencando gli ID come chiavi e impostando il valore su true, rende il controllo di una chiave semplice come leggere /users/$uid/groups/$group_id
e controllare se è null
. L'indice è più veloce e molto più efficiente dell'esecuzione di query o della scansione dei dati.