データベースを構築する

このガイドでは、FirebaseRealtimeデータベースでJSONデータを構造化するためのデータアーキテクチャとベストプラクティスのいくつかの重要な概念について説明します。

適切に構造化されたデータベースを構築するには、かなりの予見が必要です。最も重要なことは、そのプロセスを可能な限り簡単にするために、データがどのように保存され、後で取得されるかを計画する必要があります。

データの構造:JSONツリーです

すべてのFirebaseRealtimeDatabaseデータはJSONオブジェクトとして保存されます。データベースは、クラウドでホストされるJSONツリーと考えることができます。 SQLデータベースとは異なり、テーブルやレコードはありません。 JSONツリーにデータを追加すると、そのデータは、関連付けられたキーを持つ既存のJSON構造のノードになります。ユーザーIDやセマンティック名などの独自のキーを提供することも、 push()を使用して提供することもできます。

独自のキーを作成する場合は、UTF-8でエンコードする必要があり、最大768バイトにすることができ、を含めることはできません.$#[]/ 、またはASCII制御文字0〜31または127。値自体にもASCII制御文字を使用することはできません。

たとえば、ユーザーが基本的なプロファイルと連絡先リストを保存できるチャットアプリケーションについて考えてみます。一般的なユーザープロファイルは、 /users/$uidなどのパスにあります。ユーザーalovelaceには、次のようなデータベースエントリがある可能性があります。

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

データベースはJSONツリーを使用しますが、データベースに格納されているデータは、利用可能なJSONタイプに対応する特定のネイティブタイプとして表すことができるため、より保守しやすいコードを記述できます。

データ構造のベストプラクティス

データのネストを避ける

Firebase Realtime Databaseでは最大32レベルの深さのデータをネストできるため、これがデフォルトの構造であると考えたくなるかもしれません。ただし、データベース内のある場所でデータをフェッチすると、そのすべての子ノードも取得されます。さらに、データベース内のノードで誰かに読み取りまたは書き込みアクセスを許可すると、そのノードの下にあるすべてのデータへのアクセスも許可されます。したがって、実際には、データ構造を可能な限りフラットに保つことが最善です。

ネストされたデータが不適切である理由の例として、次の多重ネストされた構造を検討してください。

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

このネストされた設計では、データの反復処理が問題になります。たとえば、チャットの会話のタイトルを一覧表示するには、すべてのメンバーとメッセージを含むchatsツリー全体をクライアントにダウンロードする必要があります。

データ構造をフラット化する

代わりに、データを個別のパスに分割する場合(非正規化とも呼ばれます)、必要に応じて個別の呼び出しで効率的にダウンロードできます。この平坦化された構造を考えてみましょう。

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

会話ごとに数バイトだけをダウンロードし、UIで部屋を一覧表示または表示するためのメタデータをすばやく取得することで、部屋の一覧を繰り返すことができるようになりました。メッセージは個別にフェッチして到着時に表示できるため、UIの応答性と高速性を維持できます。

スケーリングするデータを作成する

アプリを作成するときは、リストのサブセットをダウンロードする方がよい場合がよくあります。これは、リストに数千のレコードが含まれている場合に特に一般的です。この関係が静的で一方向の場合、子オブジェクトを親の下にネストするだけです。

この関係がより動的である場合や、このデータを非正規化する必要がある場合があります。多くの場合、データの並べ替えとフィルタリングで説明されているように、クエリを使用してデータのサブセットを取得することにより、データを非正規化できます。

しかし、これでも不十分かもしれません。たとえば、ユーザーとグループの間の双方向の関係について考えてみます。ユーザーはグループに属することができ、グループはユーザーのリストを構成します。ユーザーがどのグループに属するかを決めるときになると、事態は複雑になります。

必要なのは、ユーザーが属するグループを一覧表示し、それらのグループのデータのみをフェッチするための洗練された方法です。グループのインデックスは、ここで大いに役立ちます。

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

関係をAdaのレコードとグループの両方に保存することで、一部のデータが重複していることに気付くかもしれません。現在、 alovelaceはグループの下にインデックス付けされており、 techpioneersはAdaのプロファイルにリストされています。したがって、グループからAdaを削除するには、2か所で更新する必要があります。

これは、双方向の関係に必要な冗長性です。これにより、ユーザーまたはグループのリストが数百万に及ぶ場合や、リアルタイムデータベースのセキュリティルールによって一部のレコードへのアクセスが禁止されている場合でも、Adaのメンバーシップをすばやく効率的に取得できます。

このアプローチでは、IDをキーとしてリストし、値をtrueに設定してデータを反転するため、キーのチェックは/users/$uid/groups/$group_idを読み取ってnullかどうかをチェックするのと同じくらい簡単です。インデックスは、データのクエリやスキャンよりも高速で効率的です。

次のステップ