Firebaseなしでは生きていけない昨今

Flutterでアプリを作るようになってから、Firebaseとのお付き合いが最近とみに濃くなっています。アプリにまつわる非機能要件の多くをこなしてくれるからです。どんなアプリでも鉄板で使うものと言えば、Firebase Cloud Messagingと、Firebase Authenticationだと思います。

Firebase Cloud Messaging(以下FCM)は、プッシュ通知を実現するサービスです。スマホがインフラになった時代において、プッシュ通知はもはや公共インフラ。世界で最も大規模なシステムを運用維持しているGoogleが運営してくれており、無料で使えます。

Firebase AuthenticationはFirebaseが持つ認証基盤のことで、匿名ログイン・パスワード認証ログイン・SNS/Google等の認証プロバイダーでのログイン機能を提供してくれます。アプリの場合ダウンロードしてもらうまで一苦労、そこからアカウント作ってもらうも一苦労なので、アカウント作成までの間に匿名ログインで個人(正確にはデバイスだけど)を識別してサービス運用ができるのは、とても便利です。

この2つのサービスは比較的簡単なコードで実装できます。Firebase SDKで用意されたAPIを叩くだけなので、特にFCMはセットアップにちょっとした手間が必要ですが、まぁ数時間もあれば使えます。

Firebaseを使うと使い込むの分水嶺だと思っているのが、Firebase Cloud FireStore をどこまで使っているかです。

NoSQLなFireStoreをどう使いこなすか

FireStoreを一言で言えばデータベースのクラウドサービスです。ただし、RDBではなくNoSQLです。

FireStoreのデータ操作はクライアントから直でコードを書きます。語弊はありますが、Collectionがテーブルでドキュメントがレコードに該当します。

db.collection("cities").document("LA").setData([
    "name": "Los Angeles",
    "state": "CA",
    "country": "USA"
])

FireStoreでデータをストアしてサービス運用ができるのであれば、バックエンドの運用の全てから解放されると言っても過言ではない。サーバインフラの運用がなくなるからです。

Webサーバを立てる必要ありませんし、アプリサーバも要りません。サーバ立てる必要ないので、リソース監視をする必要もありません。通常ですとアプリサーバ用のコンテナイメージ作ったりすると思いますが、それも不要。スケーラビリティはFirebaseでやってくれます。バックアップ? FireStoreのデータは自動的にGoogleが保護してくれます。

なので、FireStoreにサービスのデータを全部載せられるならそれが一番良さげなんですが、あまりそういう話を聞きません。その壁となっているのが、NoSQLは高度なデータモデリングが求められるからでしょう。

NoSQLのデータモデリングはERより難しい

RDBは列と行の2次元表で全てを表現します。そこで最も重要なのが「正規化」です。正規化という日本語が意味する内容を直感的に掴むのは難しいですけど、ライフサイクルが違うものは別のテーブルに切り出せというのが答えになります。繰り返しを排除しなさいって習いますよね、正規化の過程で。あれは端的な表現で、実践的じゃないです。

例えば上記のようなテーブルがあったとします。これの何が問題かと言うと、売上を立てる時に必ず商品と顧客の情報を追加しないといけない点です。新商品を追加するタイミングと売上が立つタイミング、同じですか?って話です。

そのため、RDBにおいては、ライフサイクルの異なるものは別テーブルに切り出して、各々が任意のタイミングで互いに干渉しないようにCRUD出来るようにする必要があります。

ERで最も重要なのがデータの重複の排除で、同じ意味を持つデータは複数のテーブルにあってはならぬになります。実務的には、テーブル分けるかカラム生やすかに集約できます。二次元表という境界線を設け、SQLの強力な集合操作をもって好きなデータを好きな条件で好きな形で出すことができます。

しかし、NoSQLはそうは行きません。サポートしてる構造はツリー構造のみです。

NoSQL(正確にはFireStore)には発行できるクエリ(SELECT文)に色々制限があります。インデックス貼らないと複数のフィールドでWHERE句が打てなかったり、IN句の候補が10件までという話から、そもそもJOINに相当する機構がありません。GroupByもないし、サブクエリもありません。RDBだと、別のテーブルに存在しないデータの集合を取ることはサブクエリで一発でできますが、その種のコードを書く場合は、自分でループ回して頑張るしかないのです。

原則としてリレーションに該当するものは、JSONのネストでいい感じに頑張るしかなく、使い所をすごく悩みます。RDBであれば、リレーションはあとからでも柔軟につけられます。NoSQLでそれは無理です。

アクセスパスを忘れるな

アクセスパスというのは/user/12みたいな、データを取得する経路のことです。FireStoreのデータ操作において、サーバーサイドプログラムは存在しません。クライアントから直接データ操作を呼び出します。

そのため、セキュリティがとても重要になります。ユーザーがデータ操作する経路にそって、ホワイトリスト方式の権限設定が必要です。例えば、ユーザーとレストランがあって、そこにレビューがあるとしましょう。/user/reviewは、そのユーザーしかアクセスできないが、/restraunts/reviewはpublicにする、みたいなイメージです。

NoSQLのデータモデリングは、データがどういう構造をたどって取得されるかに依拠します。なので、アプリでクエリされるユースケースを想定しないと何もできない上に、テーブルに該当するコレクションをJOINすることがそもそもできない。前述の方式で言うと、ユーザーにもレストランにも、レビューをもたせます。アクセスパスを担保するには、非正規化して重複データをもたせるしかないので。

NoSQLはパラダイムシフト

RDBでデータの整合性を保つのは、スキーマの仕事です。正規化されていれば、データの整合性は原則アプリケーションが頑張らなくても担保されます。NoSQLは、スキーマがありません。そのため、複数の異なるエンティティのデータの整合性を保つのは、アプリケーションの仕事となります。ERのようにエンティティ抽出してリレーション貼れば良いものじゃなく、ユースケースを想定して複雑なクエリを投げなくて済むようなツリー構造をどう作るかがキーになると感じています。

アプリだけならNoSQLで行けると思いますが、管理画面を含めた全体設計となると、まだまだ考慮不足が多くて実戦投入は難しい状況です。

でも、NoSQLが好きになってきました。パラダイムシフトを体感することは楽しいことですから。

引き続きFirebaseの真価を味わうために、FireStoreやっていこうと思います!