Struttura il tuo database

Questa guida copre alcuni dei concetti chiave dell'architettura dei dati e le best practice per strutturare i dati JSON nel tuo Firebase Realtime Database.

Costruire un database adeguatamente strutturato richiede un po' di lungimiranza. 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 Firebase Realtime Database vengono archiviati come oggetti JSON. Puoi pensare al database come a un albero JSON ospitato sul cloud. A differenza di un database SQL, non sono presenti 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 utilizzando push() .

Se crei le tue chiavi, devono essere codificate UTF-8, possono avere una lunghezza massima di 768 byte e non possono contenere file . , $ , # , [ , ] , / o caratteri di controllo ASCII 0-31 o 127. Non è possibile utilizzare caratteri di controllo ASCII nemmeno nei valori stessi.

Ad esempio, considera 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, ad esempio /users/$uid . L'utente alovelace potrebbe avere una voce nel 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

Evitare 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 recuperi i dati in una posizione nel tuo database, recuperi anche tutti i relativi nodi figlio. Inoltre, quando concedi a qualcuno l'accesso in lettura o scrittura su un nodo del tuo database, gli concedi anche l'accesso a tutti i dati sotto quel nodo. Pertanto, in pratica, è meglio mantenere la struttura dei dati quanto più piatta possibile.

Per un esempio del motivo per cui i dati nidificati non sono validi, considera la seguente struttura nidificata 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 questa progettazione nidificata, l'iterazione dei dati diventa problematica. Ad esempio, per elencare i titoli delle conversazioni in chat è necessario scaricare sul client l'intero albero chats , inclusi tutti i membri e i messaggi.

Appiattire le strutture dati

Se invece i dati vengono suddivisi in percorsi separati, detta anche denormalizzazione, possono essere scaricati in modo efficiente in chiamate separate, a seconda delle necessità. 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 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 nidificare gli oggetti figlio sotto il genitore.

A volte, questa relazione è più dinamica o potrebbe essere necessario denormalizzare questi dati. Molte volte è possibile denormalizzare i dati utilizzando una query per recuperare un sottoinsieme di dati, come illustrato in Ordinamento e filtraggio dei dati .

Ma anche questo potrebbe essere insufficiente. Consideriamo, 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 tali 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 posti.

Questa è una ridondanza necessaria per le relazioni a doppio senso. Ti consente di recuperare in modo rapido ed efficiente le iscrizioni di Ada, anche quando l'elenco di utenti o gruppi ammonta a milioni o quando le regole di sicurezza di Realtime Database 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 verificare se è null . L'indice è più veloce e molto più efficiente rispetto all'esecuzione di query o alla scansione dei dati.

Prossimi passi