Flutter×GCP/Firebaseで何かを作っていく(る)方に参考になればと思います。
上記の技術構成でリリースした Othellode というアプリは、 iOS/Android で公開済みなので、興味あればダウンロードしてみて下さい。
“しっかり”とは?
下記のような観点を考慮することを指しています。
- 運用を前提とする設計やフロー整備
- 多言語対応
- 負荷分散
- セキュリティ
- モニタリング
- 費用
- バックアップ
- ロギング
- development/staging/production 管理
これらに関する知見から、13個厳選して広く浅く書きました。
(ポイントというより感想みたいな項目もありますが..)
なお、情報漏洩対策(IP制限やBasic認証)やアセットパイプライン,BQによる分析等は、今回本腰を入れてない( 必要がなかった)ため触れませんし、詳しくありません。
目次
- Useful Information Sources
- Editor, Linter, Tools
- Firestore Data Modeling with Functions
- Service Control
- Billing Notification
- Server Side Monitoring
- Client Side Monitoring
- Parallel Processing
- Flutter State Management
- Deep Link
- Flutter Test
- Store Review
- Release Check Sheet
Useful Information Sources
Flutter も GCP/Firebase も、
公式ドキュメント + 公式動画 + 中の人の StackOverflow 回答 + 中の人のMedium や Blog + GitHub の該当 Repository
あたりをチェックするのが基本です。
とりわけ重要だと感じた情報源を貼ります。
Flutter
- flutter.dev : 言わずもがなの。手戻りを避けるために、最初に全体的に目を通しておくのが超重要だと思う。dart.dev も大事。
- flutter wiki : 例えば、初期にコーディングルールを決めると思いますが、Style guide for Flutter repo はとても参考になります。
- package 一覧 : 眺めるだけでも楽しい。
- 中の人による DartVM の解説記事
- YouTube「今週の Widget」 : とても良い。公式ドキュメントに一通り目を通していても、Widget はとても豊富で、正直最適なものを選び損ねることがあります。それを避けるのに、こういう紹介動画は最高&最高。
- YouTube「Conferece Talk」 : 最初に見ておくと、経緯込みの理解が早まる。
- YouTube「Flutter in Focus」
- YouTube「Dart」 : Dart 入門したての場合にぴったり。
- flutter samples : 公式サンプル。後は、中の人や Google のレポジトリにもいくつか参考になるものがある。google/flutter.widgets や xster/flutter-test、filiph/state_experiments など。
UI ツールとしては、Flutter Studio や DeviceDB といったものがありますが、個人的にはコードで書く方が早く+適切に組めるので使ってません。
GCP/Firebase
- 公式ドキュメント : 言わずもがなの。必ず英語で見るべきです。最新というのもありますが、日本語だと左側の Navigation が表示されず、情報収集の機会損失が激しいというのもあります。また、GCP Consle も結構触ることになるので → ドキュメント。
- YouTube Firebase チャンネル : 必要なジャンルのリストをチェックするようにする。今回で言えば、Understanding Security Rules や Get to know Cloud Firestore、Understanding Cloud Functions For Firebase、Dynamic Links など。
- https://github.com/firebase : 今回で言えば、quickstart やCloud Functions Sample、Cloud Functions Test Sample、extensions など。
Editor, Linter, Tools
本題の前に、README Tips
バッジを簡単に出せる shields の紹介です。
何千回と見ることになる README を少しでも楽しくしておく。
Editor
個人的には Android Studio をメインに使うのがオススメです。
なお、開発中は 3 つのエディター + Sumilator/Emulator 等を立ち上げる時があるので、ひ弱な PC で開発するのは無理でした。²
Android Studio: Dart, Kotlin, C++
Xcode: Swift, C++
Visual Studio: TypeScript (Functions)、Security Rules (Firestore, Storage)、Hosting 等
Android Studio は、公式の Flutter Plugin に加えて、以下も追加しておくと便利です。
Linter
flutter 自体の analysis_options.yaml や effective_dart の analysis.yaml をコピってきて、そこから必要に応じて増減させていけばいいかと思います。基本的には、制約を強く強くしていく。
参考までに、筆者の最近のはこんな感じです。if
を一文で書くのを許容する等、一部除いてるものもあります。c.f. Linter ルール一覧
Tools
リリース前の開発ガンガン期に便利なものとして、pubspec_update という package を紹介します。
簡単に言うと、yarn upgrade —-latest
と同様のことが出来ます。
いずれ使うことは無くなりますが、最初のうちはお世話になりました。³
version 固定以降は、先に貼った Plugin がとても便利です。
Firestore Data Modeling with Functions
データ構造はサービスの肝になるので、とてもとても重要です。
長くなってしまうので、記事を分けることにします。
書き終えたらリンク貼りますが、目次はざっくりこんな感じの予定です。
• gRPC on Firestore
• Authentication Trigger Functions
• NoSQL index
• Random Query
• Not Equal Query
• CollectionGroup
• Server Source vs Cache Source
• Listening Stream vs Fetching once
• Load Balancing
• Logging
• Idempotency strategy
• Security Rules vs Background Functions
• Value vs Reference vs SubCollection
• Pagination with RxDart
• Serialize/Deserialize (built_value, freezed)
• いくつかの具体例
Service Control
残念ながら、どれだけ注意しても何らかの問題は発生してしまいます。
時にそれは、ユーザのアクセスを遮断しないと復旧不可能かもしれません。また、機能変更のリリース等でアクセスを遮断する必要があるかもしれません。
こういう時にサービスを制御する機能、要はメンテや強制アプデ機能の類が重要になるわけですが、どう実現しましょうか。
RemoteConfig は単純な設定値を配信するものであり、DB へのアクセスには関与できません。
やはり Firestore の Security Rules で弾くことになります。
こんな感じで、Firestore にメンテフラグや強制アプデの情報を置きます。
そして、Security Rules でこれをチェックしてアクセス制限を実現します。必要があれば、機能別メンテも同じ要領で実現可能ではあります。
function isMaintenance() {
return get(/databases/$(database)/documents/configure/service_control).data.isMaintenance;
}
Client Side では、これらの値を Stream で Listen しておき、値に応じて画面遷移させれば ok です。
ただし、配信スピードを考慮すると、iosUpdateRequiredVersion
, androidUpdateRequiredVersion
というふうに iOS/Android 別々に持たせるのが現実的には適切だと思います。
Android でなる早に配信(強制アプデ)したいものがあった時に、App Store Connect の Review を待つ なんて羽目にならないように。
Billing Notification
残念ながら、どれだけ注意しても何らかのry)
GCP で XX 万溶かした、なんてことを防ぐために利用金額を通知しておくと良いです。
これは簡単にできます。Pub/Sub で予算通知を受け取り、以下のような Function をキックしてやればいいです。
サービスの停止もできます。詳細はドキュメント。
Server Side Monitoring
Monitoring はとても重要です。ここを整えてないと、問題発生時に迅速な対応を取れなかったり、問題の前兆を見逃したりしてしまいます。
今回で言えば、Cloud Functions や Firestore, Hosting 等を監視し、任意の閾値で Alert を上げたいわけです。
β版の機能も含みますが、Stackdriver でいくつかの metrics を簡単に取得可能なので、時間をかけずに Monitoring + Alert のシステムを構築できます。また、Slack や PagerDuty への通知も簡単に設定できます。
さらに、β版ではありますが、Alert を API で管理することができます。
例えば、Firestore の Snapshot の Listner が、10分以上 50 を超えたらインシデントだとみなすことになったとします。
まず、GCP Console で Alert を作ります。
次に、gcloud --project foo alpha monitoring policies list
を叩き、その Alert を yaml として取得します。こんな感じ。
これを Git 管理することで、Alert システムの再構築が容易になります。
なお、ただの Error 通知だけなら、Cloud Console モバイルアプリを手持ちの端末に入れておき、Stackdriver Error Reporting の通知をオンにしておけば、ある程度は事足ります。必要になったら、それも Slack に流すとかすれば良いと思います。
Client Side Monitoring
次は Client Side の Monitoring。これはほぼ package の紹介ですが…
Crash/Error
firebase_crashlytics plugin で簡単に実現可能です。
↓ こんなふうに Slack に通知を流すのも簡単に設定できます。
Analytics
firebase_analytics plugin で実現可能です。
Performance
パフォーマンスをモニタリングしたい処理があるかと思います。
firebase_performance plugin で簡単に実現可能です。
Parallel Processing
Flutter/Dart では、計算量が膨大でフレームの欠落を引き起こしてしまう処理がある場合に、Isolate を新たに spawn させるという対応が採れます。
しかしながら、おそらくユースケースのほとんどは PlatformChannel に依存しているのでは無いでしょうか。その場合が課題です。
個人的に最も注目している Issue です。
Kotlin/Swift/C++ のコードを多く含む Othellode でもこの問題にぶつかりましたが、解決/ワークアラウンド対応するのに時間がかかりそうだったのと、Othellode 固有の事情も含めて複雑度が増しそうであったため、ひとまず機能面で妥協する対応を入れてリリースしました。
今後、dart:ffi の文脈でどうなっていくのかも踏まえて詳細を調べつつ、いつか根本対応を入れようと考えています。
Kotlin/Swift/C++ の実装がビジネスロジックの根幹に関わる場合、Flutter を選ぶ前に深く調査しておくのがとても重要です。
Othellode はまさにそうだったので、予めある程度調べていて、妥協していい点ダメな点はっきりしていたので致命傷は負わずに済みました。
Flutter State Management
状態管理は保守性の観点で極めて重要です。公式ドキュメントにも書かれているように、いくつかのパターンが考えられます。
個人的には、BLoC パターンを根本思想に据えつつ、RxDart と Provider を使うのが好きです。
ものによっては ChangeNotifier メインで済ませている箇所もあります。
悩んでる場合は、ひとまず公式ドキュメントにあるシンプルな管理例を参考に組むのが断然オススメです。BLoC や Rx に手をつけるのはその後で全然問題ないです。
Reactive Programming は、やや学習コストが高い + 初めて見た時に必要性をあまり理解できなかったので、ちゃんとやったのはこの開発が初めてでした。
正直に言って感動でした。Dart が標準で Stream を備えていたり Flutter の StreamBuilder と相性が良かったりというのもありますが、新たな概念を手にした感がありました。
Rx については、ReactiveX のドキュメント や 図をいじりながら理解できる RxMarbles を見つつがんがん手を動かしていくのが良い気がします。
Stream 周りはコード量が多くなりがちなので、こんな感じで Live Template を用意するのが良いです。
一個少し悩んでいるのが、bloc に処理を投げる口として PublishSubject<dynamic>
という dynamic な Subject を定義し、依頼側で foo.add(null)
と書くケースです。
dynamic だし、add(null) だし、あまり気持ちの良いものでは無いです。それ専用の型を定義してもいっかなーと軽く検討中です。
また、bloc のライブラリはいくつか公開されていますが、筆者はどれも使わずに組みました。開発人数が多いなら採用を検討してもいいのではないでしょうか。
あと、web application との BLoC 共有の予定が無いのであれば、 BLoC パターンを 厳密に守らないのも有りだとは思いますが、そこらへんは場合によりけりですね。
Deep Link
Firebase Dynamic Links はとても便利で、アプリ内コンテンツの直接的な共有や未インストール時のストア遷移などを実現できます。
そのため、例えばタイムライン機能をアプリ内に実装せずに、Twitter や Facebook 等の既存の SNS への共有を軸にするのも、場合によってはありです。
Firebase はfoo.page.link
を用意してくれますが、独自のドメインも使用可能になっており、だいたいの要望を満たしてくれます。ちなみに、Hosting と同じドメインを利用することも可能。
ただ、小さな悩みとして、OGP 用の metadata を細かくいじれないというのがあります。
例えば、Twitter Card で Summary タイプの Card を表示させたくてもできません。summary_large_image 扱いになるので、ややタイムラインでうるさい印象が出てしまいます。(それが意図通りなら問題ないですが)
また、Dynamic Linksを作成するタイミングはユースケースで色々かと思います。
例えば、Authentication の OnCreate に Trigger して Firestore に Document を作っている場合、そのタイミングで ShareURL なるものを発行して Document に埋めたいケースは結構多いのではないでしょうか。
そういう感じでバックエンドで生成したいケースが多々あると思いますが、そこらへんの詳細は以下にまとめました。
Flutter Test
Widget Test
ケースごとの Tips は以下に書きました。
ここでは追加で 2つのことに触れたいです。
まず1つが、pumpAndSettle ではなく pump を使うことの重要さです。
ドキュメントにも書いてある通り、フレームに対する適切な意識を持ってテストを書くべきであり、基本的には pump を使うのが良いです。
これはとても重要で、テストを書く=必要なフレーム数を理解している という状態になります。テストを通して不適切な実装を発見することにも繋がります。
2つ目が、Firestore の Mock が大変ということ。
例えば、Mockito を使って DocumentSnapshot を取得するMock を書くとこんな感じになります。
ちょっと大変です。ページングとかの Mock になるともっと大変です。
ここらへんは悩んでいまして、1つ上のレイヤーつまりRepository レイヤーでマルっと Mock するのもありかなとか、いい感じにライブラリ化できないかなとか…あるいは https://pub.dev/packages/cloud_firestore_mocks あたりを利用できないかなとか
ちなみに、CI はなんでもいいですが、GitHub Actions ですごい楽に書けて体験最高だったのでリンク貼っておきます。
(Othellode は昔の名残で一部 CircleCI 使ってるんですが、いずれ全部 GitHubActions に移行するつもり)
Integration Test
これはリンクだけ貼っておしまいにします。
Store Review
Apple
【Review のはやさ】
あくまで体感ですが、昔(2017年)と比べ、審査が早くなった気がします。昔に誤ったリジェクトを受けたことがあったので警戒していましたが、今回はそのようなことも無く良かったです。
ちなみに、Othellode は商標を使用しているので、契約書類をちゃんと添付したところ、そこでのリジェクトはありませんでした。
【サインイン機能の Review】
Othellode は Apple/Google/Facebook/Twitter いずれかのサインインを必要とします。
特にコメントを添えずに申請したところ、レビュアーは以下のような行動をとりました。
- 1回目 Review: Sign in with Apple。
appreviweXXX@icloud.com
というメアドを公開する方式だった。 - 2回目 Review: Sign in with Apple。
YYY@privaterelay.appleid.com
というメアドを公開しない方式だった。 - 3回目 Review: Sign in with Facebook。明らかに偽名と分かる物だった。
ん−、Sign in with Apple だけ見てるのかなと思ったら 3 回目で Facebook を使ってきたので、Sign in with Apple を確認した後はレビュアーによりけりって感じなのかな…?
【データの投稿】
Othellode は投稿型サービスなので、Store Review でデータが投稿されることに備え、Store Review → DB 調整 → 手動公開 と進める予定でした。
しかし、Sign in with Apple 以外に DB 影響のあるアクションは行われませんでした。
Analytics を見た感じ、Sign in with Apple して画面をさらっと遷移しただけのようです。こちらとしては DB 調整不要で済んだので楽ですが。
【Review のはやさ】
はやかった。
リリース日時を制御するために、一旦クローズド版で出したところ、ややわかりにくいですが、「アプリのリリース」に審査中の旨のメッセージが表示されました。
初回は 2 日ほどかかりましたが、それ以降は2時間程度で終わることがほとんどでした。常に iOS より先に配信可能になるので、Android ユーザの多い Othellode では割とありがたいです。
ちなみに、Othellode は商標を使用していますが、Google Play には Apple Store のように契約書類を添付する場所を見つけられず(見逃した?)、ひとまず添付なしで申請しましたが通りましたね…チェックしてないのかな…
【リリース前レポートによる問題検知】
Google Play はかなり久しぶりに触ったのですが、自動チェック機能が強力になっていて嬉しかったです。
安定性やパフォーマンスを教えてくれるのですが、これを見て気付けた項目がありました。感謝。
【サインイン機能の Review】
Appleと違い、サインインは一切行われませんでした。
リリース前レポートで確認できる自動テストによるスクショを見ても、機械的に遷移できるところまでしかチェックしてないようです。
【データの投稿】
Google Play の Review ではそもそもサインインが一度も行われなかったので、必然的にデータの投稿もされたことがありません。
結果、Store Review に伴う DB 調整は一切不要でリリースできました。⁴
Release Check Sheet
Firebase Release Check List に諸々追加して作った、自作のリリースチェックリストです。
多くは本番環境を構築する時に済ませてあるものですが、改めて確認/設定します。
【 website (Firebase Hosting) 】
✅ store の privacy policy url, marketing url が正しいことの確認
✅ DynamicLinks の挙動確認
✅ Android, iOS のアプリ URL 確認【 Stackdriver 】
✅ error/warn logging の挙動確認
✅ Alert Policy の閾値が本番用になってることの確認【 Firebase Cloud Messaging 】
✅ Android, iOS ともに通知が届くことの確認
✅ 通知タップの挙動確認【 AdMob 】
✅ 広告の表示リクエスト確認 (not 表示)
✅ plugin バグ回避の確認 (c.f. FlutterFire Issue#96)
✅ リリース後数時間経ったら、store と link させる【 Firebase RemoteConfig 】
✅ 適切な値が設定されていることの確認【 Firebase Analytics 】
✅ Android, iOS ともにデータ取れることの確認【 Firebase Crashlytics 】
✅ Android, iOS ともにデータ取れることの確認【 Firebase Performance Monitoring 】
✅ Android, iOS ともにデータ取れることの確認【 Firebase Authentication 】
✅ もし必要があれば、申し込み割り当て上限を一時的に引き上げる
✅ Google/Apple/Twitter/Facebook のサインイン設定確認。 Access Permission や App Name、Description、website URL、callback等。【 Firebase Dynamic Links + Firebase Hosting 】
✅ Functions での生成が適切にできてることの確認
✅ Android, iOS, Browser でリンクを開いた時の挙動確認【 Cloud Functions 】
✅ Firestore の Backup を Storage に保存できることの確認
✅ 環境変数の確認 `firebase functions:config:get`
✅ 割り当てメモリ、ランタイム、リージョン、タイムアウトの確認【 Firestore 】
✅ 初期データの投入確認
✅ index の確認
✅ security rule の確認【 Android 】
✅ 証明書のフィンガープリント確認 (Google Play の"アプリの署名"と Firebase の"プロジェクトの設定")
✅ 配信対象国の確認
✅ aab をアップロードした後の apk サイズ確認
✅ リリース前レポートの警告確認
✅ 起動速度等のパフォーマンス確認【 iOS 】
✅ 配信対象国の確認
✅ ipa のサイズ確認
✅ 起動速度等のパフォーマンス確認【 認証情報 】
✅ GCP Console から認証情報を確認する。API Key の制限が適切か。
【 祈りの監視 】
✅ GCP Console の Error Reporting などを眺める
実は、Android の証明書周りを確認するのが当時リストから漏れていて障害を起こしたのは秘密…😇😇😇
おわりに
Flutter×GCP/Firebase は、最初の学習コストを含めると慣れた手法よりも時間がかかりますが、経験たまると開発スピードはやくなるなーと強く感じました。
【 Flutter 】
まず、iOS, Android それぞれで UI の実装をしなくていいのが本当に嬉しい。個人的にはロジックをそれぞれ実装するのは割と耐えられるのですが、UI は苦手なのもあってなるべく避けたいからです。
あと、宣言的にバシバシ書いていく気持ちよさも良かった。
そして何より、基本的な Platform 差異を Flutter チームが吸収してくれるのが本当に助かります。
会社等でのプロダクト投入も検討する価値高いと考えていますが、Issue 立てたり PR 送ったりすることに積極的な人が一人は必ずいた方がいいです(Flutter に限ったことでは無いですが)。
ちなみに、Engine の iOS 側の実装で Objective-C が使われてる都合上、Objective-C の経験ある人がいると嬉しかったりします。
今となっては、クライアントサイドは特段の理由が無い限り Flutter を採用していきたい気持ちなので、Google が Flutter 周りで収益をあげてくれると良いなと思ってます。Fuchsia とか…
個人的な課題としては、Skia とか DartVM への理解もっと深めないとなーって感じです。
【 GCP/Firebase 】
api サーバや batch サーバを構築することなくサービスを作れるのが嬉しいです。
Firestore + Functions + Authentication + α のおかげで、データのモデリングとそれに伴ういくらかのバックエンド実装に集中すれば済むため。
認証や認可まわりも自然に抑えられたり、バックエンドでの色んな処理も連携しやすかったり、とても助かります。
冪等性とかトランザクションとかインデックスとかセキュリティとかとかの重要な話もありますが、それは別個の記事で書きます。
Flutter を絡めた話題を書くと、Firestore を使う場合、データを取り扱うのがサーバサイドからクライアントサイドに移るため、そこの実装がやや面倒でした。
Flutter では、built_value や freezed を使って Firestore の Json もどきを Dart の Object に解釈しなおした上で、Write/Read を抽象化することになるはずです。
筆者が実装した時期は、今ほどパッケージが栄えていない時期だったのもあり自前で作りましたが、結構時間を食ってしまいました…(もちろん力不足の面もあります)
今では何個かパッケージも公開されているようで、この点はある程度楽になっているのかもしれません。
— — — — —
[1] この記事は 2020/02/09 時点の情報に基づいて書いてます。
[2] 2017 年モデルの Macbook ではまともに開発できなかったです。
[3] 破壊的変更やバグを含むリリースは結構あります。例えば cloud_firestore pacakge でしれっと下位互換性の無い変更を含むものがリリースされたり、Query にバグがあるものがリリースされたりしています。使い手側も十分に気を付けるのが重要です。
[4] Apple Review の際の Sign in with Apple で User Document が生成されましたが、それは特に影響無いので残したままにしました。