Firebase Summit で発表されたすべての情報をご覧ください。Firebase を使用してアプリ開発を加速し、自信を持ってアプリを実行する方法を紹介しています。詳細

データベースを構築する

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

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

適切に構造化されたデータベースを構築するには、かなりの事前準備が必要です。最も重要なことは、データを保存して後で取得する方法を計画して、そのプロセスをできるだけ簡単にする必要があることです。

データの構造: JSON ツリー

すべての Firebase Realtime Database データは 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 つの場所で更新する必要があります。

これは、双方向の関係に必要な冗長性です。これにより、ユーザーまたはグループのリストが数百万に拡大したり、Realtime Database のセキュリティ ルールによって一部のレコードへのアクセスが禁止されている場合でも、Ada のメンバーシップを迅速かつ効率的に取得できます。

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

次のステップ

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

適切に構造化されたデータベースを構築するには、かなりの事前準備が必要です。最も重要なことは、データを保存して後で取得する方法を計画して、そのプロセスをできるだけ簡単にする必要があることです。

データの構造: JSON ツリー

すべての Firebase Realtime Database データは 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 つの場所で更新する必要があります。

これは、双方向の関係に必要な冗長性です。これにより、ユーザーまたはグループのリストが数百万に拡大したり、Realtime Database のセキュリティ ルールによって一部のレコードへのアクセスが禁止されている場合でも、Ada のメンバーシップを迅速かつ効率的に取得できます。

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

次のステップ