Questa guida illustra alcuni dei concetti chiave dell'architettura dei dati e delle best practice per strutturare i dati JSON in Firebase Realtime Database.
La creazione di un database strutturato correttamente richiede una certa dose di riflessione. È fondamentale pianificare come verranno salvati i dati e come verranno recuperati in un secondo momento per semplificare il processo il più possibile.
Come sono strutturati i dati: si tratta di una struttura ad albero JSON
Tutti i dati di Firebase Realtime Database vengono archiviati come oggetti JSON. Puoi considerare il database come un albero JSON ospitato sul cloud. A differenza di un database SQL, non sono presenti
tabelle o record. Quando aggiungi dati all'albero JSON, questi diventano 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 utilizzando
push()
.
Se crei le tue chiavi, devono essere codificate in UTF-8, possono avere un massimo di 768 byte e non possono contenere .
, $
, #
, [
, ]
, /
o caratteri di controllo ASCII da 0 a 31 o 127. Non puoi utilizzare i caratteri di controllo ASCII nemmeno nei valori stessi.
Prendi ad esempio un'applicazione di chat che consente agli utenti di archiviare un profilo di base e un elenco contatti. Un tipico profilo utente si trova in un percorso, ad esempio
/users/$uid
. L'utente alovelace
potrebbe avere una voce del database simile alla seguente:
{ "users": { "alovelace": { "name": "Ada Lovelace", "contacts": { "ghopper": true }, }, "ghopper": { ... }, "eclarke": { ... } } }
Sebbene il database utilizzi una struttura ad 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ù manutenibile.
Best practice per la struttura dei dati
Evita di nidificare i dati
Poiché Firebase Realtime Database consente di nidificare i dati fino a 32 livelli di profondità, si potrebbe tendere a pensare che questa sia la struttura predefinita. Tuttavia, quando recuperi i dati in una posizione del database, recuperi anche tutti i relativi nodi secondari. Inoltre, quando concedi a un utente l'accesso in lettura o scrittura su un nodo del tuo database, concedi anche l'accesso a tutti i dati al di sotto del 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, prendi in considerazione la seguente struttura nidificata più volte:
{ // 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 nidificato, l'iterazione dei dati diventa problematica. Ad esempio, per elencare i titoli delle conversazioni di chat è necessario scaricare sul client l'intero chats
albero, inclusi tutti i membri e i messaggi.
Appiattire le strutture di dati
Se invece i dati sono suddivisi in percorsi separati, operazione chiamata anche denormalizzazione, possono essere scaricati in modo efficiente in chiamate separate, in base alle esigenze. Considera questa struttura semplificata:
{ // 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 eseguire l'iterazione dell'elenco delle stanze scaricando solo alcuni byte per conversazione, recuperando rapidamente i metadati per la visualizzazione o la visualizzazione delle stanze in un'interfaccia utente. I messaggi possono essere recuperati separatamente e visualizzati man mano che arrivano, consentendo all'interfaccia utente di rimanere reattiva e veloce.
Creare dati scalabili
Quando crei app, spesso è meglio scaricare un sottoinsieme di un elenco. Questo è particolarmente comune se l'elenco contiene migliaia di record. Quando questa relazione è statica e unidirezionale, puoi semplicemente nidificare gli oggetti secondari sotto l'oggetto principale.
A volte, questa relazione è più dinamica o potrebbe essere necessario denormalizzare questi dati. Spesso puoi denormalizzare i dati utilizzando una query per recuperare un sottoinsieme di dati, come descritto in Recupero dei dati.
Tuttavia, anche questo potrebbe non essere sufficiente. Prendi in considerazione, ad esempio, una relazione bidirezionale tra utenti e gruppi. Gli utenti possono far parte di un gruppo, mentre i gruppi costituiscono un elenco di utenti. Quando arriva il momento di decidere a quali gruppi appartiene un utente, le cose si complicano.
È necessario un modo elegante per elencare i gruppi a cui appartiene un utente e recuperare solo i dati relativi a questi gruppi. Un indice di gruppi può essere di grande aiuto in questo caso:
// 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 sia nel gruppo. Ora alovelace
è indicizzato in un gruppo e techpioneers
è elencato nel profilo di Ada. Pertanto, per eliminare Ada
dal gruppo, è necessario aggiornare la voce in due posizioni.
Si tratta di una ridondanza necessaria per le relazioni bidirezionali. Consente di recuperare in modo rapido ed efficiente le iscrizioni di Ada, anche se l'elenco di utenti o gruppi scala fino a milioni o quando le regole di sicurezza di Realtime Database impediscono l'accesso ad alcuni record.
Questo approccio, invertendo i dati mediante l'elenco degli ID come chiavi e impostando il valore su true, semplifica il controllo di una chiave come leggere /users/$uid/groups/$group_id
e controllare se è null
. L'indice è più veloce
e molto più efficiente rispetto all'esecuzione di query o alla scansione dei dati.