このガイドでは、データ アーキテクチャのいくつかの主要な概念と、Firebase Realtime Database で JSON データを構造化するためのベスト プラクティスについて説明します。
適切に構造化されたデータベースを構築するには、かなりの事前準備が必要です。最も重要なことは、データを保存して後で取得する方法を計画して、そのプロセスをできるだけ簡単にする必要があることです。
データの構造: JSON ツリー
すべての Firebase Realtime Database データは JSON オブジェクトとして保存されます。データベースは、クラウドでホストされる JSON ツリーと考えることができます。 SQL データベースとは異なり、テーブルやレコードはありません。 JSON ツリーにデータを追加すると、既存の JSON 構造のノードになり、キーが関連付けられます。ユーザー ID やセマンティック名などの独自のキーを提供することも、 childByAutoId
を使用してそれらを提供することもできます。
独自のキーを作成する場合は、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
かどうかをチェックするだけで、キーのチェックが簡単になります。インデックスは、データのクエリやスキャンよりも高速で、かなり効率的です。