Strukturieren Sie Ihre Datenbank

In diesem Leitfaden werden einige der wichtigsten Konzepte der Datenarchitektur und Best Practices für die Strukturierung der JSON-Daten in Ihrer Firebase Realtime Database behandelt.

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

Wie Daten strukturiert sind: Es ist ein JSON-Baum

Alle Firebase Realtime Database-Daten 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 der JSON-Struktur Daten hinzufügen, wird sie zu einem Knoten in der vorhandenen JSON-Struktur mit einem zugeordneten Schlüssel. Sie können Ihre eigenen Schlüssel wie Benutzer-IDs oder semantische Namen bereitstellen oder diese mit push() für Sie bereitstellen.

Wenn Sie Ihre eigenen Schlüssel erstellen, müssen diese UTF-8-codiert sein, dürfen maximal 768 Byte groß sein und dürfen keine . , $ , # , [ , ] , / , 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, die es Benutzern ermöglicht, ein grundlegendes Profil und eine Kontaktliste zu speichern. Ein typisches Benutzerprofil befindet sich in einem Pfad wie /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 in der Datenbank gespeicherte Daten als bestimmte native Typen dargestellt werden, die den verfügbaren JSON-Typen entsprechen, um Sie beim Schreiben von besser wartbarem Code zu unterstützen.

Best Practices für die Datenstruktur

Verschachtelung von Daten vermeiden

Da die Firebase Realtime Database die Verschachtelung von Daten mit einer Tiefe von bis zu 32 Ebenen ermöglicht, könnten Sie versucht sein zu denken, dass dies die Standardstruktur sein sollte. Wenn Sie jedoch Daten an einem Speicherort in Ihrer Datenbank abrufen, rufen Sie auch alle ihre 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, die Datenstruktur so flach wie möglich zu halten.

Ein Beispiel dafür, warum verschachtelte Daten fehlerhaft sind, sehen Sie in der folgenden mehrfach verschachtelten 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 das Durchlaufen der Daten problematisch. Zum Beispiel erfordert den Titel der Chat - Konversationen Auflistung den gesamten chats Baum, darunter alle Mitglieder und Nachrichten, auf den Client heruntergeladen werden.

Datenstrukturen abflachen

Wenn die Daten stattdessen in separate Pfade aufgeteilt werden, auch Denormalisierung genannt, 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 Byte pro Konversation heruntergeladen werden, um schnell Metadaten zum Auflisten oder Anzeigen von Räumen in einer Benutzeroberfläche abzurufen. Nachrichten können separat abgerufen und beim Eintreffen angezeigt werden, sodass die Benutzeroberfläche reaktionsschnell 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 dem übergeordneten Objekt verschachteln.

Manchmal ist diese Beziehung dynamischer, oder es kann erforderlich sein, diese Daten zu denormalisieren. In vielen Fällen können Sie die Daten denormalisieren, indem Sie eine Abfrage verwenden, um eine Teilmenge der Daten abzurufen, wie in Abrufen von Daten beschrieben .

Aber auch das kann nicht ausreichen. Betrachten Sie beispielsweise eine wechselseitige Beziehung zwischen Benutzern und Gruppen. Benutzer können einer Gruppe angehören, und Gruppen umfassen eine 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, zu denen ein Benutzer gehört, und nur Daten für diese Gruppen abzurufen. Ein Index von Gruppen kann sehr viel helfen hier:

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

Sie werden möglicherweise feststellen, dass dadurch einige Daten dupliziert werden, indem die Beziehung sowohl unter Adas Datensatz als auch unter der Gruppe gespeichert wird. Jetzt ist alovelace unter einer Gruppe indiziert und techpioneers in Adas Profil aufgeführt. Um Ada aus der Gruppe zu löschen, muss sie also an zwei Stellen aktualisiert werden.

Dies ist eine notwendige Redundanz für wechselseitige Beziehungen. Es ermöglicht Ihnen, die Mitgliedschaften von Ada schnell und effizient abzurufen, 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, der die Daten invertiert, indem die IDs als Schlüssel /users/$uid/groups/$group_id und der Wert auf true gesetzt wird, macht die Suche nach einem Schlüssel so einfach wie das Lesen von /users/$uid/groups/$group_id und das Prüfen, ob er null . Der Index ist schneller und viel effizienter als das Abfragen oder Scannen der Daten.

Nächste Schritte