Struttura il database

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 correttamente strutturato richiede un po' di lungimiranza. Ma soprattutto, devi pianificare il modo in cui salvare i dati recuperate in seguito per semplificare il più possibile il processo.

Come sono strutturati i dati: si tratta di un 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 questi ultimi possono essere forniti utilizzando il metodo push().

Se crei le tue chiavi, queste devono avere codifica UTF-8, può essere un valore massimo di 768 byte e non può contenere il controllo ., $, #, [, ], / o ASCII 0-31 o 127. Non puoi utilizzare caratteri di controllo ASCII nei valori da soli.

Ad esempio, prendiamo in considerazione 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 che ha un aspetto simile a questo:

{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      "contacts": { "ghopper": true },
    },
    "ghopper": { ... },
    "eclarke": { ... }
  }
}

Sebbene il database utilizzi una struttura JSON, i dati archiviati nel database possono rappresentati come determinati tipi nativi che corrispondono ai tipi JSON disponibili per 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 nel tuo database, recuperi anche da tutti i suoi nodi figlio. Inoltre, quando concedi a un utente l'accesso in lettura o scrittura a un nodo nel tuo database, concedi anche l'accesso a tutti i dati nodo. Di conseguenza, nella pratica, è meglio mantenere la struttura dei dati piatta il più 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.

Appiattisci le strutture di dati

Se i dati vengono invece suddivisi in percorsi separati, chiamata anche denormalizzazione, può essere scaricato in modo efficiente in chiamate separate, se necessario. Prendi in considerazione questa struttura bidimensionale:

{
  // 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 puoi scorrere l'elenco delle stanze virtuali scaricando solo un pochi byte per conversazione, recuperando rapidamente i metadati per elencare o visualizzare in una UI. I messaggi possono essere recuperati separatamente e visualizzati all'arrivo, consentendo alla UI 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 oggetti secondari sotto l'elemento principale.

A volte questa relazione è più dinamica oppure può essere necessario denormalizzare questi dati. Molte volte puoi denormalizzare i dati utilizzando una query di recuperare un sottoinsieme di dati, come illustrato in Recupera i dati.

Ma anche questo potrebbe non essere sufficiente. Considera, ad esempio, una relazione bidirezionale tra utenti e gruppi. Gli utenti possono far parte di un gruppo e i gruppi sono costituiti da un elenco di utenti. Quando è 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 Recuperare solo i dati per 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 alcuni dati vengono duplicati archiviando la relazione sia nel record di Ada sia nel gruppo. Ora alovelace è indicizzato con un e techpioneers 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. Ti permette di recuperare in modo rapido ed efficiente le iscrizioni di Ada, anche se l'elenco di utenti o consente la scalabilità di milioni di gruppi o quando Realtime Database regole di sicurezza impedire l'accesso ad alcuni record.

Questo approccio, invertendo i dati mediante l'elenco degli ID come chiavi e impostando il valore valore true, semplifica il controllo di una chiave come la lettura /users/$uid/groups/$group_id e controllo che sia null. L'indice è più veloce e molto più efficiente rispetto all'esecuzione di query o alla scansione dei dati.

Passaggi successivi