iOS
Firebase

Firebaseを使い始めたら人生が変わった(ような気がした)

最近、僕の周りでFirebaseを使った人たちは、

「サーバーの処理をFirebaseに置き換えたら、筋肉がつきました!」(29歳, 社会人)
「Firebaseを使ったら友達も増えてわっしょいわっしょい」(11歳, 小学生)
「もうFirebase無しじゃ生きられない」(38歳, エンジニア)
「にゃーん」(1歳4ヶ月, うちの猫)

と言っていました。嘘です。
冗談はさておき、趣味で開発しているアプリのサーバーサイドをFirebaseに置き換えたらすごく良かったので、ずらずらと書いていきたいと思います。

Firebaseとは?

Firebaseは、Googleが運営しているmBaaSで、iOS/AndroidアプリからWebサービスまで幅広く使えます。
リアルタイム同期や豊富な機能が特徴で、サーバーサイドの開発コストを大幅に減らすことができます。

色々機能はあるのですが、今回僕が使った機能は以下のものになります。

Firebase Authentication
 アカウント機能。
 メールアドレスからSNSアカウント、電話番号認証を利用してアカウントの管理ができます。
 匿名アカウントの作成もできます。強い

Cloud Firestore
 リアルタイムで同期ができるデータベース。2018年8月現時点ではまだベータ版となっています。
 似たものでRealtime Databaseというものがありますが、
 データモデルの違いやスケーラビリティ、パフォーマンスなどなど全体的にFirestoreの方が進化しているため、
 ベータ版でも問題ないよ!という方にはFirestoreをオススメしています。
 データベースを選択: Cloud Firestore または Realtime Database

Cloud Storage
 Firebase向けのGoogle Cloud Storage
 ユーザー作成した画像やファイルをアップロード/ダウンロードできます。
 無料だと5GBまで使えます。

Firebase Hosting
 静的ホスティングサービス
 GoogleのCDNの恩恵が得られます。
 無料だと1GBまで使えます。

Cloud Functions
 Firebase向けのCloud Functions
 Auth, FirestoreやStorageのイベントをフックして使うことができます。
 適当なメソッドを用意してアプリから直接呼ぶこともできます。
 また無料プランだと外部との通信ができない点に注意してください。

Analytics, Crashlytics
 ユーザーがどのViewControllerを何秒見ていた、アプリがどの場面でクラッシュしたかが解析できます。
 今までアナリティクス系の導入に抵抗があって入れてなかったのですが、
 今回試しに入れてみたらこの機能はあまり使われてないなとかわかるようになり、
 今後どの機能を中心に追加していくか、などの計画が立てられるようになりました。
 もっと早くから入れておけば良かった。

なぜFirebaseを使おうと思ったのか

新機能で、アカウント機能とバックアップ機能の実装を考えていて、

「いちいちサーバーで実装するのもだるいなぁ」
「Firebaseいいじゃん…」
「どうせだから全部Firebaseに任せてみっかあ」
となりました。

Firebaseは無料で利用できる枠もかなり緩いため、趣味でやる場合にも最適です。

Firebaseを使う「前」

趣味で開発しているアプリのサーバーサイドは、以下のAPIを実装していました

  • 内部データのアップデート更新確認

    • ファイルのバージョン、アップデートの内容、ファイルサイズがDBに記録されている
    • アプリは現在の内部データのバージョンを渡すだけ。
    • PHP7 + Slim3製。渡されたバージョンと最新のバージョンを比較した結果を返す
  • 内部データ(静的ファイル)

    • CDNに配置してある
  • 寿司を奢る機能

    • DBにカウント数を記録
    • 呼び出すたびに+1して結果を返す

ちなみに寿司を奢る機能というのは、ボタンを押すと1つ寿司が奢れる(カウント数が増える)というもの。完全にネタ機能です。

01.png

また新機能の要件はこんな感じでした。

  • アカウント管理

    • Twitter連携で手軽にアカウント作成・ログインしたい
  • バックアップ機能

    • ローカルのファイルをストレージにアップロード/ダウンロードできるようにして、バックアップ機能として利用できるようにしたい

Firebaseを使った「後」

あら不思議。全ての機能がFirebaseで完結しました。

  • 内部データのアップデート更新確認

    • Firestoreでファイルの情報、格納先URL(Hosting)とアップデート内容を管理
    • アプリ側でローカルデータのバージョンと比較するクエリを実行してもらう
    • 最新の情報があれば、通知を出す
  • 内部データ(静的ファイル)

    • Firebase Hostingを使用
    • GoogleのCDNの恩恵が得られる
  • 寿司を奢る

    • Firestoreでカウント数を管理
    • アプリ側で寿司を増やしてもらう(理由については後述)
  • アカウント管理

    • Firebase Authを使用
    • Twitterコンシューマーキーとシークレットキーを用意する
    • GoogleアカウントやGithubアカウント、電話番号認証もいけるため機会があれば使いたい
  • バックアップ機能

    • Storageを使用
    • アカウントごとに管理するデータになるので、ルールもしっかり書く
    • 転送量や使用量を削減するため、圧縮してから送ったり、送信できるサイズに制限をかけたりしています。

Firebaseを使った感想

  • インフラ管理を気にすることが無くなり、夜眠れるようになった
  • 組み込みが楽
  • 自前で実装する手間が省けて猫を撫でる時間が増えた

Firebaseを使って詰まったところ

ここからは、実際に実装する際に詰まったところを書いていきます。

Cloud Functionsが遅い(時がある)

 当初、寿司を奢る機能を実装するとき、
 Firestoreを操作するメソッドを用意し、Functionsにデプロイ、アプリ側から直接呼び出せるようにしたのですが、
 レスポンスに10秒くらいかかることがありました。
 調べてみると、しばらくAPIの呼び出しがないと実行リソースが抑えられるため、復帰するまで時間がかかる、とのこと
 本番環境ならAPIが割と頻繁に呼ばれるだろうから問題ないよ!とはあったのですが、
 寿司も奢られるタイミングは不定期なので、アプリ側からFirestoreを直接操作するようにしました。

Twitter連携がうまくいかなかった

 SafariやTwitterアプリで連携後、アプリに戻る処理がうまく動きませんでした。
 色々調べてみたところ、Firebaseのメソッド入れ替えを無効化すると動きました。

Info.plist
<dict>
    <key>FirebaseAppDelegateProxyEnabled</key>
    <false/>
</dict>

内部データのアップデート更新確認がうまくいかなかった

 Firestoreで新しいバージョンがないかどうかを確認するクエリを実行したのですが、
 これがうまく動作しない問題がありました。

let ref = Firestore.firestore().collection("resource")
            .whereField("version", isGreaterThan: localVersion)
            .whereField("debug", isEqualTo: false)
            .order(by: "version", descending: true)

 ドキュメントを再度確認すると以下の通りに書いてありました。

ただし、等価演算子(==)と範囲比較(<、<=、>、>=)を組み合わせる場合は、必ずカスタム インデックスを作成してください。

 isGreaterThanとisEqualToを指定しているのでバッチリ当てはまっていました😂
 インデックスを作成すると無事データが取れるようになりました。

Firebaseを使う上で気をつけたほうがいいなと思ったこと

Firebaseは楽ですが、気をつけないといけないところもあります。

セキュリティルールを設定する

 クライアントサイドに任せる部分が多いため、セキュリティルール・バリデーションはしっかりやっときましょう

アプリの最低要求バージョンを定義する

 どんなに厳密に設計をしていても、やむを得ない仕様変更が発生することはあると思います。
 サーバーサイドを開発している状況であれば、APIの仕様変更を除けば、基本的にサーバー側の調整で済みますが、
 Firestoreなどはアプリ側で操作することが多いため、
 プロパティ名など依存する部分を変更すると、旧バージョンで新しい情報が取得できない、といった問題が発生します。

 そのため、FirestoreやRemoteConfigを利用して強制アップデートバージョン、
 あるいは最低要求バージョンを定義し、アプリ側で比較処理をし、古ければアラートを出すといった処理を追加しておくと安心だと思います。

 ちなみに僕のアプリでは、起動時にアラートを出してアップデートを迫るのが嫌なので、
 バックアップ機能など重要な機能を使う時のみ出すようにしています。

アカウント削除後の処理を実装する

 ここら辺は開発するアプリにもよると思いますが、
 削除されたアカウントの固有データを残しておくのはリソース的にもプライバシーの観点でもあまりよろしくないです。
 Functionsを利用して、アカウントが削除された時の処理を追加しておくと良いと思います。

 Githubに手軽に使えるスクリプトが公開されていますので、こちらを使うと楽です。
 firebase/user-privacy

まとめ

Firebaseは神。使わない手はない