Help us understand the problem. What is going on with this article?

Firebase Realtime Databaseを使う手順 〜データ設計からセキュリティ、組み込みまで〜

More than 1 year has passed since last update.

最近FirebaseのRealtimeDatabaseを触っているので、手順やポイントをまとめてみた。

Realtime Databaseとは

Firebase Realtime Datbaseとは、Googleが提供するNoSQLデータベースサービスであり、接続された全てのアプリ・Web等でデータの変更がリアルタイムに反映されるもの。
即時性が必要なチャットアプリとかに最適。

導入

Firebaseのアカウント作成

Firebaseにアクセスして手順通りにステップを踏むだけでOK。
あとは左ペインの"Database"に行くとGUIでKey:Valueを追加していくことが出来る。

データ構造の設計

このページを詳しく読めば大体の事が書いているので、設計に入る前に一読した方がよさそう。

RDBとは使われ方も性質も全くことなり、非正規化を意識して出来るだけフラットなデータ構造にする必要がある。
例えば、以下のような場合を考えてみる。(極端な例)
ユーザーのグループか何かがあって、グループに属するメンバー/年齢/自己紹介文があり、Bobの自己紹介文だけがすごい長い。
アプリでは、ユーザー一覧画面として名前のみをリストで見せたい要件があった場合、以下のような構造だと必要のない年齢や長過ぎるBobの自己紹介文までFetchしてしまう。(/something/groups/group_a/users でアクセスすると、子ノード含む全ての情報をFetch)

良くない例
{
  "something": {
    "groups": {
      "group_a": {
        "users": {
          "Alice": {
            "age": "25",
            "introduction": "Hello, this is Alice."
          },
          "Bob": {
            "age": "50",
            "introduction": "Hello, this is Bob. I'm from California and blah blah........長い文章" 
          }
        }
      },
      "group_b": {
        "users": {
          "Steve": {
            "age": "20",
            "introduction": "Hello, this is Steve."
          },
          "Joey": {
            "age": "28",
            "introduction": "Hello, this is Joey."
          }
        }
      }
    }
  }
}

これを、出来るだけフラットにしてみる。
こうすることで、/something/groups/group_a にアクセスすると、属するメンバーの名前だけが取得され無駄なBobの自己紹介文は取得しなくて済む。
group <-> userの間で双方向に参照を持たせることで、以下のようなことも実現可能になる。

  • 自分が属するグループのメンバーを取得する
  • 自分がどのグループに属しているかを取得する
  • Steveがgroup_aに属しているかを判断したい場合、/something/group_a/Steve でnullが返れば属していないと判断出来る。

また、フラット化されていない例だと、どのグループに属しているかを判断するためには、すべてのデータを舐めないといけない。

フラット化した例
{
  "something": {
    "groups": {
      "group_a": {
        "Alice": true,
        "Bob": true
      },
      "group_b": {
        "Steve": true,
        "Joey": true
      }
    },
    "user_list": {
      "Alice": {
        "groups": {
          "group_a": true
        }
      },
      "Bob": {
        "groups": {
          "group_a": true
        }
      },
      "Steve": {
        "groups": {
          "group_b": true
        }
      },
      "Joey": {
        "groups": {
          "group_b": true
        }
      }
    },
    "user_info": {
      "Alice": {
        "age": "25",
        "introduction": "Hello, this is Alice."
      },
      "Bob": {
        "age": "50",
        "introduction": "Hello, this is Bob. I'm from California and blah blah........長い文章"
      },
      "Steve": {
        "age": "20",
        "introduction": "Hello, this is Steve."
      },
      "Joey": {
        "age": "20",
        "introduction": "Hello, this is Joey."
      }
    }
  }
}

データのデプロイ

データ構造が決定したら、Firebase Reatime Databaseのページにアクセスして、作ったjsonをそのままインポートすることも可能。
JavaやNode.jsのサーバー用SDKも公開されているので、自作ツールを作ることもできる。(別の投稿で記載予定)

アプリで使う(Androidの例)

アプリでの使い方は非常に単純。まずはgradleにrealtime databaseの依存関係を記述する。

build.gradle
compile 'com.google.firebase:firebase-database:10.0.1'
compile 'com.google.firebase:firebase-auth:10.0.1' // <- 認証使わない場合はなくてもOK(後述)

基本的な使い方

以下のような構造のデータを取得することを想定する。

サンプル
{
  "root": {
    "something": "foo"
  }
}
読み出し
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("something");
myRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(final DataSnapshot dataSnapshot) {
        String foo = dataSnapshot.getValue(String.class)
        // foo => "foo"
    }

    @Override
    public void onCancelled(final DatabaseError databaseError) {
    }
});
書き込み
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("something");
myRef.setValue("poge!");

オブジェクトになっている場合はクラスを作る。

{
  "root": {
    "something": {
      "foo": "bar",
      "hoge": "fuga"
    }
  }
}
同じ構造のクラス作成
public class Somethig {
    private String foo;
    pirvate String hoge;
    // getter / setterは省略
}
読み出し
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("something");
myRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(final DataSnapshot dataSnapshot) {
        Something something = dataSnapshot.getValue(Something.class)
        String foo = something.getFoo();
        String hoge = something.getHoge();
        // foo => "bar"
        // hoge => "fuga
    }

    @Override
    public void onCancelled(final DatabaseError databaseError) {
    }
});

onDataChange は初回のアタッチ時と、あとはFirebaseのコンソール上のJSONを変更すると瞬時にアプリ側に通知される。
ただ、ここをListenしていると、子ノード含む全てのデータがfetchされるらしい。
データが大量にある場合は、転送量が多くなるため要注意。次の用にListenするノードを指定したりして最適化すると良いと思われる。

必要な子ノードだけListenする

ValueEventListener ではなく ChildEventListenerというものを使うとその部分だけの変更を検知することが出来る。

子ノード付き
{
  "root": {
    "something": {
      "some_child": {
        "foo": "bar"
      },
      "hoge": {
        "puge": "page"
      }
    }
  }
}
子ノードだけ
// some_childの変更だけをListenし、上記の"puge"の変更は検知しない。
DatabaseReference childRef = database.getReference("some_child");
childRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(final DataSnapshot dataSnapshot, final String s) {

    }

    @Override
    public void onChildChanged(final DataSnapshot dataSnapshot, final String s) {

    }

    @Override
    public void onChildRemoved(final DataSnapshot dataSnapshot) {

    }

    @Override
    public void onChildMoved(final DataSnapshot dataSnapshot, final String s) {

    }

    @Override
    public void onCancelled(final DatabaseError databaseError) {

    }
});

セキュリティ

セキュリティを設定しないと意図しないユーザーからデータが取得されたり、書き込みがあったりなど想定外の事態を招く可能性が高い。
その為、しっかりとしたセキュリティ設計が大事。
FirebaseのRealtime Databaseコンソールの「ルール」タブから確認が可能。

ルール(例)
// 全部のノードに対して書き込みも読み出しも可能(やらないほうが良い)
{
  "rules": {
    ".read": true,
    ".write": true
  }
}

// 全部のノードに対して読み出しのみ可能(やらない方が良い)
{
  "rules": {
    ".read": true,
    ".write": false
  }
}

// /something以下に対して読み出し可能、書き込み不可
// /somethingと同レベルの他のノードは読み書き不可
{
  "rules": {
    ".read": false,
    ".write": false,
    "something": {
      ".read": true,
      ".write": false
    }
  }
}

また、変数も組み込まれており、以下のように指定することも可能。

変数使ってみる
// uidが一致した場合のみ書き込み可能
{
  "rules": {
    "users": {
      "$uid": {
        ".write": "$uid === auth.uid"
      }
    }
  }
}

// 書き込みはアドミンの場合のみ可能
// NodeのSDKとかで管理側のみが書き込める場合なんかに有効
{
  "rules": {
    "something": {
      ".read": true,
      ".write": "auth != null && auth.isAdmin == true"
    }
  }
}

// 認証されたユーザのみ読み出し可能
// 基本的に全ユーザー読み出し可能だが、publicに公開したくない場合に有効
// この後にアプリで認証する場合を記載
{
  "rules": {
    "something": {
      ".read": "auth != null",
      ".write": "auth != null && auth.isAdmin == true"
    }
  }
}

アプリで匿名認証と組み合わせてRealtime Databaseにアクセスする

セキュリティに一番最後のruleを適用した場合に、アプリ側で認証を使いアクセスを行わなければならない。
ここではFirebase Authenticationの匿名認証を用いて試してみる。
※最初の方のgradleに記載した firebase-auth はここで使うもの。

匿名認証してからアクセス
mAuth = FirebaseAuth.getInstance();
mAuth.signInAnonymously()
        .addOnCompleteListener(new OnCompleteListener<AuthResult>() {
            @Override
            public void onComplete(@NonNull final Task<AuthResult> task) {
                // anonymous login complete.
                // ここでRealtime Databaseの処理を行う。
                // auth payloadがRealtime Databaseに渡されて認証される。
                // Firebase Authenticationの画面でUIDなどの一覧が見える。
                // ここでは匿名認証だが、他にもtwitterやfacebookなども使うことが出来るらしい
                readData();
            }
        });

public void readData() {
    FirebaseDatabase database = FirebaseDatabase.getInstance();
    DatabaseReference myRef = database.getReference("something");
    myRef.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(final DataSnapshot dataSnapshot) {
            // 続きの処理
        }

        @Override
        public void onCancelled(final DatabaseError databaseError) {
        }
    });
}

一通り使ってみて

最初の設計さえ念入りに行えば、バックエンドの処理は全てFirebaseが行ってくれる。
リアルタイム性が必要とされるサービスで組み合わせるとかなり強力なツールとなりそう。
ただ、やはりNoSQLについてはリレーションが複雑なデータ構造には不向きなようなので、RDBを使うべきかどうかも合わせて検討した方がよい。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away