Strukturieren Sie Ihre Datenbank

Dieser Leitfaden behandelt einige der Schlüsselkonzepte der Datenarchitektur und Best Practices für die Strukturierung der JSON-Daten in Ihrer Firebase-Echtzeitdatenbank.

Der Aufbau einer ordnungsgemäß strukturierten Datenbank erfordert einiges an Voraussicht. Am wichtigsten ist, dass Sie planen, wie Daten gespeichert und später abgerufen werden, um diesen Vorgang so einfach wie möglich zu gestalten.

So sind Daten strukturiert: Es handelt sich um einen JSON-Baum

Alle Daten der Firebase-Echtzeitdatenbank werden als JSON-Objekte gespeichert. Sie können sich die Datenbank als einen in der Cloud gehosteten JSON-Baum vorstellen. Im Gegensatz zu einer SQL-Datenbank gibt es keine Tabellen oder Datensätze. Wenn Sie Daten zum JSON-Baum hinzufügen, werden diese zu einem Knoten in der vorhandenen JSON-Struktur mit einem zugehörigen Schlüssel. Sie können Ihre eigenen Schlüssel wie Benutzer-IDs oder semantische Namen bereitstellen oder diese mithilfe von childByAutoId für Sie bereitstellen.

Wenn Sie Ihre eigenen Schlüssel erstellen, müssen diese UTF-8-codiert sein, dürfen maximal 768 Byte lang sein und dürfen keine enthalten . , $ , # , [ , ] , / oder ASCII-Steuerzeichen 0-31 oder 127. Sie können auch keine ASCII-Steuerzeichen in den Werten selbst verwenden.

Stellen Sie sich beispielsweise eine Chat-Anwendung vor, mit der Benutzer ein Basisprofil und eine Kontaktliste speichern können. Ein typisches Benutzerprofil befindet sich unter einem Pfad, beispielsweise /users/$uid . Der Benutzer alovelace könnte einen Datenbankeintrag haben, der etwa so aussieht:

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

Obwohl die Datenbank einen JSON-Baum verwendet, können die in der Datenbank gespeicherten Daten als bestimmte native Typen dargestellt werden, die den verfügbaren JSON-Typen entsprechen, um Ihnen beim Schreiben besser wartbaren Codes zu helfen.

Best Practices für die Datenstruktur

Vermeiden Sie die Verschachtelung von Daten

Da die Firebase-Echtzeitdatenbank die Verschachtelung von Daten mit einer Tiefe von bis zu 32 Ebenen ermöglicht, könnten Sie versucht sein zu glauben, dass dies die Standardstruktur sein sollte. Wenn Sie jedoch Daten an einer Stelle in Ihrer Datenbank abrufen, rufen Sie auch alle untergeordneten Knoten ab. Wenn Sie jemandem Lese- oder Schreibzugriff auf einen Knoten in Ihrer Datenbank gewähren, gewähren Sie ihm außerdem Zugriff auf alle Daten unter diesem Knoten. Daher ist es in der Praxis am besten, Ihre Datenstruktur so flach wie möglich zu halten.

Betrachten Sie als Beispiel dafür, warum verschachtelte Daten fehlerhaft sind, die folgende mehrfach verschachtelte Struktur:

{
  // 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": { ... }
  }
}

Bei diesem verschachtelten Design wird die Iteration durch die Daten problematisch. Um beispielsweise die Titel von Chat-Konversationen aufzulisten, muss der gesamte chats Baum, einschließlich aller Mitglieder und Nachrichten, auf den Client heruntergeladen werden.

Datenstrukturen verflachen

Wenn die Daten stattdessen in separate Pfade aufgeteilt werden, was auch als Denormalisierung bezeichnet wird, können sie bei Bedarf effizient in separaten Aufrufen heruntergeladen werden. Betrachten Sie diese abgeflachte Struktur:

{
  // 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": { ... }
  }
}

Es ist jetzt möglich, die Liste der Räume zu durchlaufen, indem nur wenige Bytes pro Konversation heruntergeladen werden, und so schnell Metadaten zum Auflisten oder Anzeigen von Räumen in einer Benutzeroberfläche abzurufen. Nachrichten können separat abgerufen und bei ihrem Eintreffen angezeigt werden, sodass die Benutzeroberfläche reaktionsfähig und schnell bleibt.

Erstellen Sie skalierbare Daten

Beim Erstellen von Apps ist es oft besser, eine Teilmenge einer Liste herunterzuladen. Dies ist besonders häufig der Fall, wenn die Liste Tausende von Datensätzen enthält. Wenn diese Beziehung statisch und unidirektional ist, können Sie die untergeordneten Objekte einfach unter den übergeordneten Objekten verschachteln.

Manchmal ist diese Beziehung dynamischer oder es kann erforderlich sein, diese Daten zu denormalisieren. Oftmals können Sie die Daten denormalisieren, indem Sie eine Abfrage verwenden, um eine Teilmenge der Daten abzurufen, wie unter „Daten abrufen“ beschrieben.

Aber selbst das kann unzureichend sein. Stellen Sie sich beispielsweise eine wechselseitige Beziehung zwischen Benutzern und Gruppen vor. Benutzer können einer Gruppe angehören und Gruppen bestehen aus einer Liste von Benutzern. Wenn es darum geht, zu entscheiden, zu welchen Gruppen ein Benutzer gehört, wird es kompliziert.

Was benötigt wird, ist eine elegante Möglichkeit, die Gruppen aufzulisten, denen ein Benutzer angehört, und nur Daten für diese Gruppen abzurufen. Ein Gruppenverzeichnis kann hier sehr hilfreich sein:

// 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
      }
    },
    ...
  }
}

Möglicherweise stellen Sie fest, dass dadurch einige Daten dupliziert werden, da die Beziehung sowohl im Ada-Datensatz als auch in der Gruppe gespeichert wird. Jetzt ist alovelace unter einer Gruppe indexiert und techpioneers ist in Adas Profil aufgeführt. Um Ada aus der Gruppe zu löschen, muss sie an zwei Stellen aktualisiert werden.

Dies ist eine notwendige Redundanz für wechselseitige Beziehungen. Damit können Sie die Mitgliedschaften von Ada schnell und effizient abrufen, selbst wenn die Liste der Benutzer oder Gruppen in die Millionen geht oder wenn Sicherheitsregeln der Echtzeitdatenbank den Zugriff auf einige der Datensätze verhindern.

Dieser Ansatz, bei dem die Daten durch Auflisten der IDs als Schlüssel und Festlegen des Werts auf „true“ invertiert werden, macht die Suche nach einem Schlüssel so einfach wie das Lesen /users/$uid/groups/$group_id und die Prüfung, ob er null ist. Der Index ist schneller und wesentlich effizienter als das Abfragen oder Scannen der Daten.

Nächste Schritte