React Native Firebaseを本番運用した時に試行錯誤したことをまとめていきます。
Firebaseとは
主にモバイルやweb開発においてbackendやインフラをマネージドしてくれるサービスです。
FaaSのような簡単なバックエンドから他DB, Storage, Push通知、広告計測などの機能をAPI経由で利用することができ、簡単なアーキテクチャのサービスであればほとんどFirebaseを用いて開発を完結できる程度に色々な機能を兼ね備えています。
どのような機能が存在しているかについては今回は説明を省きますので、興味がある方は実際に使ってみるのが良いでしょう。無料枠でも多くのサービスが利用できます。公式サイトはこちら
主に登壇資料にまとめてあるあれこれから抜粋した内容にもなるので、興味があればそちらもご覧いただけたら幸いです。
React Native Firebase
React Native FirebaseとはFirebaseのiOS, Android SDKをラップしてReact Nativeから利用できるようにしたライブラリです。
javascriptが利用できるReact Nativeの世界ではJavascript SDKのfirebaseを利用することもできます。
bare workflowが必要である点でjavascript sdkよりもネイティブの知識が必要になっていますが、analyticsやpush通知などをNativeのAPIを利用することができます。
github上の機能対応表は以下の通りです。
Firebase Features | RNFirebase | Web SDK |
---|---|---|
AdMob | ✅ | ❌ |
Analytics | ✅ | ❌ |
App Indexing | ❌ | ❌ |
Authentication | ✅ | ✅ |
-- Phone Auth | ✅ | ✅ |
Core | ✅ | ✅ |
-- Multiple Apps | ✅ | ✅ |
Cloud Firestore | ✅ | ✅ |
Cloud Messaging (FCM) | ✅ | ❌ |
Crashlytics | ✅ | ❌ |
Dynamic Links | ✅ | ❌ |
Functions Callable | ✅ | ✅ |
Invites | ✅ | ❌ |
Instance ID | ✅ | ❌ |
Performance Monitoring | ✅ | ❌ |
Realtime Database | ✅ | ✅ |
-- Offline Persistence | ✅ | ? |
Remote Config | ✅ | ❌ |
Storage | ✅ | ✅ |
特にanalyticsやpush通知は代替機能がexpoにもあるものの、Dynamic Linksはかなり強力な機能なので、個人的にはwebをアプリで再実装するような要件のアプリではなくUXを洗練させたいならReact Native Firebaseを利用することをお勧めします。
実際にどう本番で使っていたか
以下の機能を使ってアプリ開発をしました。
- Authentication
- Cloud Firestore
- Realtime Database
- Dynamic Links
- Crashlytics
- Cloud Messaging (FCM)
- Functions Callable
- Storage
特に
- Authentication
- Cloud Firestore
- Realtime Database
- Dynamic Links
- Storage
および、対応表には紹介されていませんが、Firebase Hostingという機能を用いてweb管理画面を作りました。この時React Native Firebaseのロジック層とweb sdkのロジック層をDIのように実装することおで同じコードでライブラリを切り替えるだけでwebでも使えるようにしていました。
具体的にはReact NativeとReact Nativeが採用しているようなライブラリにaliasを貼って切り替えるロジックを採用していました。
以下いくつか汎用的に使われるスタックについてそれぞれの機能の要点と使い所を解説していきます。
Authentication
ユーザー登録および、管理者用アカウントを作成するのに利用しました。
特にセキュリティルールという仕組み(概要については拙著の過去記事をご覧ください)を使って実装しました。
CustomClaimsと呼ばれる認証ユーザー特有に紐付けられるフラグやIDデータを用いて簡単なユーザーロールを作成できます。
また、SNSログインなどの認証情報をアプリで利用することができます。GoogleとFacebookについてはReact Native用のSDKラッパーがあるので、APIを実装するのがとても簡単でした。
注意すべきこと
最初に以下の設計方針を固めておく必要があります。
- ユーザーがどのようなログイン方法を取れるか
- ログイン方法によってアカウントを分けるかどうか
特に後者はfirebaseのデフォルトの設定ではメールアドレスが違う場合は別アカウントとみなされ、同じアカウントにログイン情報を紐づけるためには作ってしまった認証情報を削除してから紐づける必要があります。
特にメールアドレスが同じでも違うログイン情報を用いた場合は別アカウントとして登録することを許容する場合は、コンソールページから設定を変更する必要があり、変えてから運用するとほぼ不可逆的な設定変更となるので特に気をつける必要があります。
Authenticationよもやま話
その他にGithub Loginを実装したのですが、こちらはFirebase自体にはtokenを認証できるプロバイダーが存在しているのですが、トークン取得のAPIのネイティブ実装がなかったために当時は自前で実装する必要がありました。
当初のやり方としてはWebView経由でWeb認証フローに則ってGitHub Sign inページにアクセスさせ認証完了時のcallback_urlによってWebViewのurl onChangeイベントで受け取りそのcallback_urlをfunction callableによって、firebase functionに送って処理させることで、tokenを受け取りfirebaseのプロバイダにadmin sdk経由で登録するという手法を取りました。
ただし、WebViewではセッション情報をアプリ外に持ち込むのでハンドリングの扱いに気をつける必要があることと、UX的にはややユーザーに認知負荷がかかる方法です。
そこでcustom schemeを使ってredirect callbackをDeep Linkで受け取り、アプリ内のハンドリングを行うという方法の方が自然な実装の流れになります。(おそらくGoogleとFacebookのSDKが採用しているのもこの方法だと思います。)
このDeepLink実装は後のDynamic Linksを使って機能させることができます。
どのような機能を利用していたかという詳細についてはReact Native Tokyo(旧React Nativeにゆかりのある企業が集まる会)で登壇した時の資料があるので興味があればご覧いただければと思います。
Cloud Firestore, Realtime Database
この二つはほぼ用途が同じなので、まとめて解説します。
Firebase上で扱えるNoSQLデータベースです。
これらの接続権限はFirebase AuthのuidやそれぞれのDB情報から
前述のセキュリティルールによって制御します。
そのため、ユーザーアカウントデータを扱う場合はAuthenticationの利用はほぼ必須です。
この二つの違いは、
Firestore
- IO数課金である
- 複合インデックスを作成することで複数条件のfilterとsort条件を組み合わせられる
- collection, sub collectionという概念によって、権限管理ができる
Realtime Database
- 保存データサイズに関して従量課金である
- 一つの巨大なオブジェクトをハッシュアクセスする前提で設計されている
- collectionとkeyの対応関係が曖昧
- sortができる
という特徴があります。また、共通の性質として
- transaction管理ができる
- snapshotをlistenすることによって、localとfirebase上のデータのオンライン同期ができる
- offline キャッシュが使える。(オフラインモード対応ができる)
その他
- セキュリティルールの書き方が異なる
という違いがあります。
使い分けは基本的にfirestoreの方が高機能なので、firestoreを利用しつつIO操作が頻繁に行われるが、ほとんどデータサイズの大きさが変わることのないものに関してはrealtime databaseを使うと良いでしょう。
私たちのケースでは、ユーザーのpush通知デバイストークンのようなrefreshが必要で頻繁に通信が発生するものに関してはrealtime databaseを利用していました。また、バッチの最終実行時間を記録するstoreとしても利用していました。
気をつけなければいけないのは、複合条件でfilterやソートができるとは言うものの動的に作成される属性値に対しての条件とsortの複合条件でクエリすることができません。なので、idのような動的に生成された値をkeyもしくは配列として複数の値を設定したフィルター条件指定をしてupdated_atの順番を指定することはできません。
これは、レコードによって異なる値のキーはインデックスを作成してもフィルター条件として他のレコードをひっかけることができず、複合条件として利用すると、そのキーを持つレコードの中でsort処理が行われるためです。
つまり、何らかの生成された値で条件を指定したい場合は、固定の属性(property)名を使用するか、storeのデータを検索エンジンなどに同期して別の枠組みの中で処理を実行する必要があります。
また、ユーザーが直接firestore,およびrealtime databaseのレコードを処理する場合、その権限に気をつける必要があります。
ユーザーが好きな値を入れてはいけないようにする仕組みはsecurity ruleを属性ごとにバリデーションすると言う方法もありますが、もしその制約が複雑すぎたり、多くのレコードに対して詳細に設定する必要がある場合は、後述のfunction callableを用いて、バックエンドにサーバレスのエンドポイントを作成してadmin sdkによって操作する方が良いでしょう。
具体的には、初期のユーザーレコードの作成や、アプリケーション上でのユーザーステータスの遷移を行う場合などはfunction callableでCloud Function上での操作の方が適していたりします。
Dynamic Links
これはReact Native FirebaseでDeep Link対応するのに使いました。これはjavascript sdkでは実現できません。
使い方はfirebaseの管理画面で自分のモバイルアプリ上で端末が一意にアプリを識別するためのdynamic linkを登録して、アプリ上でcustom schemeやintent上で呼べるようにします。
特にmail link認証とverificationの仕組みなどを組み合わせて、エンドポイントから起動前または起動後のアプリに遷移してメールリンクで新規導線作成を行った後、追加の新規情報入力を必須にしてから認証を承認するといったような高度なUXを実現できます。
詳細は先ほどの資料をご覧ください。
Cloud Messaging (FCM)
こちらもReact Native Firebaseでしか使えない機能になります。
利用のためにAppleStoreおよびGoogle Play用にキーを設定する必要があり、多くのネイティブ知識とpush通知の仕組みを理解する必要があり、細かくユーザーターゲットを設定して通知したり通知アイコン二度のような情報を反映させるか等の機能を作る事は結構大変です。
アプリ起動時ととアプリ離脱時にも通知がくるようにするために特定のイベントリスナーやデバイストークン設定をアプリ起動時に設定をする必要があります。
同じ端末でもアプリをアンインストールされると、デバイストークンが変わるために複数端末対応などを考えるとどのようにトークンマネジメントを行うか仕組みを考える必要があります。
この際、私たちのアプリでは起動時にデバイストークン検証を行うロジックを入れることにしたので、頻繁なアクセストークンに関するIOが発生するためトークンはfirestoreではなくrealtime database上に保存していました。
Functions Callable(Cloud Function)
いくら直接データベースの操作権限があるからと言って、endpointの実装を避けては通れないものも多いです。
特に認証系やステータス系の処理やバッチなどのトリガーはCloud Functionを利用することが必要です。
実運用時ではfirestoreおよび、realtime databaseのバックアップ作成や検索エンジンへのレコード送信のトリガーに使ったりしました。アプリ側でエンドポイントを利用する際はhttpCallable APIにデプロイした関数名と引数を指定するだけなので利用はとても簡単です。
TipsとしてはOASみたいな仕様定義がないのでtypescriptなどでバックエンドの仕様の変更と追従させる仕組みをつくるとよいでしょう。
どのようなマネジメントをしていたかについてはこちらの資料もご覧ください。
この機能のおかげでただリクエストを捌くだけの簡単な機能であれば、バックエンドのためのインスタンスノードを立ち上げる必要がなくなるのでインフラ構築にかかるコストが格段に低くなります。
ただしデメリットとしてはコールドスタンバイ方式を採用しているFaaSなので、リクエストが頻繁ではないエンドポイントは初回のスピンアップに多少時間がかかる点です。
もしどんな時でもアクセスのレイテンシを落とさないことが重要なアプリケーションであればそれに対応する仕組みを考える必要があります。
Storage
こちらはAPI経由でファイルを送信保存したり、ダウンロードする仕組みを作れます。アプリ内で極力キャッシュしたくないデータや動的データはここに保存します。
また、セキュリティルールを設定できるので、firestoreやauthの情報とfilePathの関係を使ってアクセス権限を細かく制御できます。
特にこちらはRNのFileSystemからファイルを読み込んでblobを送信してdownloadURLを発行すると言う手順になります。
storageはCDNを有効にもできます。
まとめ
React Nativeで使えるFirebaseの機能をざっくり説明しました。
リリースしたアプリはFirebase + 検索エンジンだけの構成で問題なくモバイルアーキテクチャのアプリの運用をすることができたので、今後RNでアプリ開発をする方の選択肢の一つにしていただけたら幸いです。