Questa guida illustra alcuni dei concetti chiave dell'architettura dei dati e le best practice per la strutturazione dei dati JSON in Firebase Realtime Database.
La creazione di un database con una struttura corretta richiede una certa pianificazione. L'aspetto più importante è pianificare la modalità di salvataggio e recupero dei dati per semplificare il più possibile questo processo.
Come sono strutturati i dati: un albero JSON
Tutti i dati Firebase Realtime Database vengono archiviati come oggetti JSON. Puoi considerare
il database come un albero JSON ospitato nel cloud. A differenza di un database SQL, non esistono
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,
ad esempio ID utente o nomi semantici, oppure possono essere fornite automaticamente utilizzando
push().
Ad esempio, considera un'applicazione di chat che consente agli utenti di archiviare un profilo di base
e un elenco di contatti. Un profilo utente tipico si trova in un percorso, ad esempio
/users/$uid. L'utente alovelace potrebbe avere una voce di database che
assomiglia 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.
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à, potresti essere tentato di pensare che questa debba essere la struttura predefinita. Tuttavia, quando recuperi i dati in una posizione del database, recuperi anche tutti i relativi nodi secondari. Inoltre, quando concedi a qualcuno l'accesso in lettura o scrittura a un nodo del database, gli concedi anche l'accesso a tutti i dati presenti in quel nodo. Pertanto, in pratica, è preferibile mantenere la struttura dei dati il più semplice possibile.
Per un esempio del motivo per cui i dati nidificati sono dannosi, considera la seguente struttura a più livelli:
{ // 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 questa progettazione nidificata, l'iterazione dei dati diventa problematica. Ad
esempio, per elencare i titoli delle conversazioni di chat è necessario scaricare l'intero chats
albero, inclusi tutti i membri e i messaggi, sul client.
Semplifica le strutture dei dati
Se i dati vengono 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 scorrere l'elenco delle stanze 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 man mano che arrivano, consentendo all'interfaccia utente di rimanere reattiva e veloce.
Crea 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. Molte volte puoi denormalizzare i dati utilizzando una query per recuperare un sottoinsieme dei dati, come descritto in Recuperare i dati.
Ma anche questo potrebbe non essere sufficiente. Considera, 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.
È necessario un modo elegante per elencare i gruppi a cui appartiene un utente e recuperare solo i dati di questi gruppi. Un indice dei 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 questa operazione 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 aggiornarlo in due posizioni.
Questa è una ridondanza necessaria per le relazioni bidirezionali. Ti consente di recuperare in modo rapido ed efficiente le appartenenze di Ada, anche quando l'elenco di utenti o gruppi raggiunge milioni di elementi o quando Realtime Database regole di sicurezza impediscono l'accesso ad alcuni record.
Questo approccio, che inverte i dati elencando gli ID come chiavi e impostando il
valore su true, semplifica il controllo di una chiave: basta leggere
/users/$uid/groups/$group_id e verificare se è null. L'indice è più veloce
e molto più efficiente rispetto all'esecuzione di query o alla scansione dei dati.