データベースの構造化

準備

Firebase Realtime Database を使用するには、Firebase プロジェクトを作成してから、Firebase Unity SDK パッケージを Unity プロジェクトに追加する必要があります。

設定

前提条件

Android

  • Unity 5.0 以降
  • Android NDK バージョン 10d 以降

iOS

  • Unity 5.0 以降
  • Xcode 8.0 以降

Unity プロジェクトをまだ用意していない場合は、いずれかのクイックスタート サンプルをダウンロードし、特定の Firebase 機能をお試しいただけます。クイックスタートを使用する場合は、次のステップでバンドル識別子が必要になるため、プロジェクト設定からバンドル識別子を忘れずに取得してください。

Firebase コンソールでアプリを設定する

アプリに Firebase を追加するには、Firebase プロジェクトと、アプリ用の Firebase 構成ファイルが必要です。

Firebase プロジェクトをまだ用意していない場合は、Firebase コンソールで Firebase プロジェクトを作成します。モバイルアプリと関連付けられた既存の Google プロジェクトがある場合は、[Google プロジェクトをインポート] をクリックします。それ以外の場合は、[プロジェクトを追加] をクリックします。

Android

  1. [Android アプリに Firebase を追加する] をクリックし、設定手順に沿って操作します。既存の Google プロジェクトをインポートする場合、このステップは自動的に実行されることがあります。その場合は、構成ファイルをダウンロードするだけでかまいません。
  2. 求められたら、アプリのパッケージ名を入力します。必ずアプリで使用しているパッケージ名を入力してください。パッケージ名を設定できるのは、アプリを Firebase プロジェクトに追加するときだけです。
  3. 指示されたら google-services.json ファイルをダウンロードします。このファイルはいつでも再ダウンロードできます。
  4. このファイルを、プロジェクトのアセット フォルダ内の任意の場所にコピーします。

iOS

  1. [iOS アプリに Firebase を追加する] をクリックし、設定手順に沿って操作します。既存の Google プロジェクトをインポートする場合、このステップは自動的に実行されることがあります。その場合は、構成ファイルをダウンロードするだけでかまいません。
  2. 求められたら、アプリのバンドル ID を入力します。必ずアプリで使用しているバンドル ID を入力してください。バンドル ID を設定できるのは、アプリを Firebase プロジェクトに追加するときだけです。
  3. 指示されたら GoogleService-Info.plist ファイルをダウンロードします。このファイルはいつでも再ダウンロードできます。
  4. プロジェクトに GoogleService-Info.plist ファイルを追加します。

    • Firebase コンソールからダウンロードした GoogleService-Info.plist を、Unity プロジェクトの任意のフォルダにドラッグします。

アプリに Firebase Unity SDK を追加する

  1. Firebase Unity SDK をダウンロードします。
  2. [Assets] > [Import Package] > [Custom Package] メニュー項目を選択します。
  3. 先にダウンロードした Firebase Unity SDK から FirebaseDatabase.unitypackage パッケージをインポートします。
  4. [Import Unity Package] ウィンドウが表示されたら [Import] をクリックします。

SDK を初期化する

Android の Firebase Unity SDK には Google Play 開発者サービスが必要であり、SDK を使用する前に最新版にしておく必要があります。次のコードをアプリケーションの先頭に追加して、SDK で他のメソッドを呼び出す前に Google Play 開発者サービスを確認し、必要であれば、Firebase Unity SDK で必要とされるバージョンに更新します。

Firebase.FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task => {
  var dependencyStatus = task.Result;
  if (dependencyStatus == Firebase.DependencyStatus.Available) {
    // Set a flag here indiciating that Firebase is ready to use by your
    // application.
  } else {
    UnityEngine.Debug.LogError(System.String.Format(
      "Could not resolve all Firebase dependencies: {0}", dependencyStatus));
    // Firebase Unity SDK is not safe to use here.
  }
});

アプリをビルドする

Android

  1. [File] > [Build Settings] メニュー項目を選択します。
  2. [Platform] リストから [Android] を選択します。
  3. [Switch Platform] をクリックし、ターゲット プラットフォームとして [Android] を選択します。
  4. Unity ステータスバーの右下隅にあるスピナー(コンパイル中)アイコンが停止するまで待ちます。
  5. [Build and Run] をクリックします。

iOS

  1. [File] > [Build Settings] メニュー項目を選択します。
  2. [Platform] リストから [iOS] を選択します。
  3. [Switch Platform] をクリックし、ターゲット プラットフォームとして [iOS] を選択します。
  4. Unity ステータスバーの右下隅にあるスピナー(コンパイル中)アイコンが停止するまで待ちます。
  5. [Build and Run] をクリックします。

データの構造化

このガイドでは、Firebase Realtime Database のデータ アーキテクチャの主な概念と JSON データを構築するおすすめの方法を紹介します。

適切な構造のデータベースを構築するには、事前の綿密な計画が必要です。データの保存方法と後で取得する方法をあらかじめ計画し、このプロセスをできる限り簡単にしておくことが最も重要です。

データの構造: JSON ツリー

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

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

たとえば、基本的なプロフィールと連絡先リストを保存できるチャット アプリケーションについて考えてみましょう。通常のユーザー プロフィールは /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 のインデックスが作成され、Ada のプロフィールに techpioneers グループがリストされています。したがって、Ada をグループから削除するには、2 か所で更新を行う必要があります。

これは、双方向の関係に必要な冗長性です。ユーザーやグループのリストが数百万件の規模に拡張された場合や、Realtime Database セキュリティ ルールで一部のレコードへのアクセスが禁じられている場合でも、この冗長性によって Ada のメンバーシップをすばやく効率的にフェッチできます。

この手法では、ID をキーとしてリストして値を true に設定することで、データを反転させていますが、そうすることで、/users/$uid/groups/$group_id を読み取ってそれが null であることを確認するだけで、キーがないかのチェックが済むようにしています。インデックスはデータのクエリやスキャンを実行するよりも迅速かつ効率的です。

次のステップ

フィードバックを送信...

ご不明な点がありましたら、Google のサポートページをご覧ください。