【感想】『りあクト! Firebaseで始めるサーバーレスReact開発』: #りあクト でmBaaSへ
表紙は親密度の上がった笑いあう二人。尊い…(違)
技術同人誌の『りあクト!』3部作と続編も読んだので、5作目を読みました。
今回はこれまでのReact開発の知見を活かし、BaaSあるいはmBaaSの代表格Firebaseにバックエンドをお任せし、世の中に公開していく実際のサービスをサーバーレスで開発していく本となっています。今回もまたまた本文は会話形式で読みやすいです。
- 表紙は親密度の上がった笑いあう二人。尊い…(違)
- 第1章 プロジェクトの作成と環境構築
- 第2章 Seed データ投入スクリプトを作る
- 第3章 Cloud Functions でバックエンド処理
- 第4章 Firestore を本気で使いこなす
- 第5章 React でフロントエンドを構築する
- 第6章 Firebase Authentication によるユーザー認証
- まとめ:Firebaseを使ったサーバーレス開発がわかる本
第1章 プロジェクトの作成と環境構築
Reactをマスターして作中で無事に一人前のフロントエンジニアに成長できた秋谷さん。しかし仕事で回ってくるのは既存プロダクトのフロントエンドのモダン化だったりサーバーサイドは別エンジニアにお伺いを立てねばならなかったりで、理想通りには行ってない模様。(このへん、自社サービス中心の会社さんだと実際どうなのでしょうね)
そこへ、エライ人をノせるのが上手い柴埼先輩が持ってきたのがサービスとして当たれば大きい試験的プロジェクト。メンバーはこの2人で自由、バックエンドはBaaSの代表格Google Firebase
に全部お任せ。サービス名は Mangarel
「マンガレル」。→よし、やろう! という事で物語が始まります。
柴埼先輩は漫画好きという属性がここで明かされています。だから「~つらくないReact」開発3部作でコード内にあれだけネタが仕込んであった訳ですね……
1-1. 基本環境を作る
Google Cloud Function
はNode.jsの12系に対応していないので10系も使うため、anyenv
,nodenv
を使って複数バージョンに対応。(Windowsならnvm-windows
でしょうか。)Create React App
でTypeScriptのプロジェクトを新規生成。tsconfig.json
はパイセン式の設定を追加。functions/
というディレクトリも除外設定に追加。ESLint
とPrettier
でコードフォーマットを統一。typesync
というライブラリを追加、> typesnyc
で自動で必要なTypeScriptの型ファイルをpackage.json
のdevDependencies
に追加してくれる。(これ便利そうですね)- 設定ファイル
.eslintrc.js
等々を準備。柴埼先輩スペシャル版が用意済み。 - gitコミット時にコマンドを走らせられるライブラリ
husky
、コミット前にESLint
を実行できるlint-staged
をインストール、コマンドから実行できるようにpackage.json
に設定追加。
1-2. Firebase プロジェクトの作成と初期化
- ブラウザから
Firebase
の管理コンソールにログイン。この時URL末尾の/u/{0からの数字}/
がログインした順番になっているので、アカウントが複数ある場合は注意。(こういう話もありがたいです) - プロジェクト名を決める時に
Firebase
上で一意のプロジェクトIDも一緒に決まるので注意。誰かと被っていると末尾に乱数のポストフィックスがついてしまう。 - リソースロケーション(≒AWSのリージョン)の設定は一度きりなので注意。東京なら
asia-northeast1
。 Create React App
で作成済みのローカルのプロジェクトフォルダに行ってnpmからfirebase-tools
をインストール。これがFirebase CLI
でログイン後にいろいろできる。> firebase init
で対話形式でいろいろ選んでいくとプロジェクトが初期化。Reactの初期画面はもう表示可能。
1-3. Cloud Functions の環境整備
- プロジェクトフォルダの
functions/
の中がCloud Functions
の仕事場。サポートしているNode.js
のバージョンは最新が8だが10もベータ版サポート。ほぼ動くとのこと。 tsconfig.json
を変えたりテストフレームワークのJest
を入れたりLint
とPrettier
を入れたり.eslintrc.js
を整備したり。このへんも柴埼先輩スペシャル版が用意済み。functions/
のように作ったフォルダは上の階層とは設定が別々になるのに注意。
1-4. 独自ドメインを設定する
- Firebaseコンソールから設定できる。
- Firebaseプロジェクトの実体は
Google Cloud Platform
のプロジェクトとして存在しているので、GCPのコンソール画面からもAPIキーが再作成できたりする。
さらっと流されていますが、柴埼先輩スペシャルの準備済み設定ファイル群はここまで揃うのにずいぶん苦労したのではないかなあと……
Firebaseコンソールの管理画面はもう日本語されていて分かりやすいです。作中で秋谷さんが言っているように用語がけっこう独特ですね。
第2章 Seed データ投入スクリプトを作る
2-1. データベースの作成と Admin 環境の整備
- モデル(≒RDBのテーブル、Firebase上の実態はコレクション)は本のbooks、著者のauthors、出版社のpublishersの3つということで進んでいきます。
- データベースの新規作成はFirebaseコンソール画面から。
Realtime Database
は古くCloud Firestore
が今。 - 秘密鍵が生成できてローカルの
functions/
配下に置くので、これはGitHubなどに置かないように注意。
2-2. データ投入スクリプトの作成
- Firebaseでのデータ自動投入の仕組みはありそうで実はないということで、Node.js界隈でコマンドを作る際のライブラリの代表格、
Commander.js
を使って自作していきます。
- ここから呼ばれるコマンドの実体、
dbseed.ts
の中ではCSVファイルをパースするライブラリcsv-parse
も利用。秘密鍵を使ってFirestoreに接続、CSVの中身をパース、1件ごと登録……というのをNode.js 10から使える新しい構文を活用したサンプルコードになっています。
- FirestoreではRDBのテーブル→コレクション。RDBの1レコード→1ドキュメント。こういう用語回りは本当にややこしいですね……。(AWSの
DynamoDB
だと、RDBのテーブル→テーブル、1レコード→1アイテム) - Firestoreは統計処理周りが弱くてレコード数のカウントが全件検索でないとできない、同一ドキュメントの書き換えは1秒1回までなど制限あり。
2-3. npm scripts として登録する
- VSCodeのコマンドパレット、
Command(Ctrl) + shift + b
で立ち上がるビルドを使ってウォッチタスクで実行する方法。 - DBへの登録状況はFirebaseコンソールからも確認できるということで、ドキュメント全件削除の命令がコンソール画面上で間違って実行しやすいところにあるという怖い話も載っています。
- 一括削除は
CLI
からやるのが普通ということで、オプションを付けると子供のコレクションも再帰的に削除可能。
第3章 Cloud Functions でバックエンド処理
3-1. 簡単な HTTP 関数を作ってみる
Google Cloud Function
はAWSのLambda
相当で後を追ってリリースされたGCPのサービス。その後、Firebaseの機能のひとつとして改めてリリースされたのがCloud Functions for Firebase
で、よりFirebaseの機能と統合・最適化されている。Cloud Functions
の関数の本体はsrc/index.ts
などに書いたJS(TS)コード。firebase-functions
,firebase-admin
をimportして、初期化→リージョン指定→コレクションのこれを取得します→レスポンスに設定 を書く。- 秋谷さんが驚嘆しているようにコードはけっこう短いです。
Lambda
関数と似た感じですね。 - デプロイはかなり時間が掛かるので注意。
> firebase serve
を使うとローカル上でコマンド実行でURLが出力されるので、そこをブラウザで見てもローカルホスト上で結果が確認できる。
3-2. クローラーをスケジュール設定関数にする
- Amazonと楽天のAPIを定期的に叩いて新刊を見ていきます。このスクレイピング用途では、『りあクト!TypeScriptで極める現場のReact開発』でも登場した
Puppeteer
が一番人気。プロジェクトフォルダのfunctions/
の下でインストール、index.ts
の中で定期実行するコードを書いていきます。 - スケジュール指定のコードが
crontab
と同じなのが面白い。タイムゾーンでAsia/Tokyo
を指定することに注意。 - オプション指定がけっこう多いのと、起動時のメモリ指定もコードの中で指定します。デフォルト256MBというのも
Lambda
関数と同じで、Puppeteer
が動くには1-2GBはいるとのこと。 Puppeteer
のAPIもいろいろあるそうですが、本書のサンプルにある基本的な関数を使えばDOM要素の取得には足りるとのことです。- また、クローリングした後でFirebaseに登録する前にFirebase上の対象コレクションを全検索、ない場合にのみ登録という冪等性の担保を行っています。
- スケジュール設定した関数を手元で動かすときは
Functions Shell
というコマンドから行えるツールあり。
クラウドとサーバーレス全般で「冪等性」はよく登場しますが、Firebaseでも同じく重要なのだなと。
スクレイピングといえば言語的にはPython
なイメージがあったのですが、JS界隈だとPuppeteerがデフォルトなんですね。
細かいネタとしては秋谷さんは『炎炎ノ消防隊』が好きなのが判明します!
3-3. スケジュール設定関数をデプロイする
- けっこうなコード量になった
index.ts
をデプロイしていきます。 - 前述の
Functions Shell
から関数を実行するとまずインデックスがないと怒られる→
示されたURLに行くとコンソール画面に繋がってそこでインデックスが作成→
無料枠だとGoogle以外のネットワークに繋がらない制限あり(!)なのでFirebaseを有料プランに変更→
> firebase deploy
のコマンド実行でデプロイ。 - 知らないでやったら躓きそうなところがけっこうあります……
3-4. Cloud Functions 中上級編 Tips
> firebase functions:config:set {キー}={"値"}
で環境変数を指定。> firebase functions:config:get
で.runtimeconfig.json
と指定すると環境変数をファイルに落とせる。firebaseのコマンド群はこのファイルを見てくれる。関数内からも参照可能。変数名は大文字不可。- 本番実行時に
process.env
の中を見るとNODE_ENV: 'production'
が入っているが、ローカルサーバーだとundefined
になってしまう。 - スケジュール設定のコード内でタイムゾーンを指定しても本番サーバー内のタイムゾーンは変わらない。柴埼先輩テクだと
date-fns-timezone
ライブラリを使う手がある。 - 作りたいサービスのReactアプリ本体と
functions/
配下のCloud Functions
で関数を共通化して使いたい場合は、柴埼先輩がいろいろ試してたどり着いた邪道テクではシンボリックリンクを張る手法で解決。Reactアプリ本体側のコードが本体、functions/
配下はそこへリンクを張る。 Cloud Functions
は最初の起動時にindex
にある関数をすべてロードしてしまうので、index.ts
が巨大化していくと遅くなる。分割しておくとよい。また起動時にprocess.env.FUNCTIONS_TARGET
という変数に関数名が入るので、その対象の関数だけexportsするというテクがある。
大まかにはAWS Lambda
でサーバーレス開発をしていく時と似たような雰囲気ですね。
準備された設定ファイルや細かなTipsなどなど、柴埼先輩のこれまでの試行錯誤(と、その裏にある本書の作者さんの苦労)が入っているので本としてはスラスラ進んでいます。でもFirebaseは進化中のサービスであるだけにけっこう細かいところにまだまだ躓きポイントや罠があるんだなあという感じです。
第4章 Firestore を本気で使いこなす
4-1. Firestore と RDB の違いと各種制限について
- RDBのテーブルと似ているがコレクションは基本的にスキーマレスなので好きなフィールドを定義できてしまう。柴崎先輩はTypeScriptの組み込み変数
Partial
型を使ったテクを使用。 - ドキュメントID以外のフィールド値はユニーク制限がない。RDBのレコード同士が外部キーで結合しているようなパターンは、データを非正規化して片方のドキュメント(≒レコード)に持たせる。更新が掛かったら後から定期的に
Cloud Function
で更新するなど。 count()
ができない弱点は…作中の2人の予想通り、後からBigQueryへのエクスポートや連携ができるようになった。
firebase.google.com support.google.com
- データのUPDATE,DELETEは1件づつ。batchは500件まで。
- RDBはカラムを指定してデータの一部を取得できるが、Firestoreはドキュメント全体しか取得できない。
- 他にもドキュメントの最大容量やフィールド数、書き換えは1秒に1回まで...など制限いろいろ。
4-2. Firestore のクエリーとインデックス
- クエリーは等価評価と範囲比較の2種があり、1つのクエリーでは範囲比較はひとつのフィールドでしか行えない。(!)
- OR検索や != もなし。検索用フラグを持たせたりしてうまくやる必要がある。
- なおOR検索に相当する
in
、array-contains-any
が後から提供された。
- RDBのように
create index
しなくても元から全フィールドにインデックスがある。昇順と降順が別。配列用はまた別インデックス。範囲の比較と等価の比較を同時に行うときはまた別インデックス。 - コレクションの親子関係をまたがって検索するときはコレクショングループインデックスというのがある。
- コレクション定義の外側で別にインデックスが定義されているらしく、コレクションを削除してもインデックスは消えない。(RDBのテーブルだと
drop table
で一緒に消える)
4-3. Firestore の配列の取り扱い
- 配列検索の
array-contains
, 配列要素追加のarrayUnion()
, 要素削除のarrayRemove()
がコード中から書ける。 array-contains
はひとつのクエリーで1回しか使えない。booksコレクションの中でauthorIdsの配列を見てauthorIdがA先生、は検索できるがA先生かB先生両方、は検索できない。(!)- ここで柴崎先輩が編み出したのが昔の公式推奨だった「Trueマップ」方式。配列でなくMapで
authorMap: {'A先生のID': true, 'B先生のID': true}
のように定義すると、検索句で==
,true
を2回書けば実現できる。
4-4. Firestore で日時を扱う際の注意
- JS/TSの
Date
型でデータを突っ込むと、Firebase独自のfirebase.firestore.Timestamp
型に変換されて保持、その後検索時もそのまま。TypeScriptを使うときはモデル定義で最初からこのTimestamp
型で定義しておいたほうがよい。 - Firebase側の
FieldValue.serverTimestamp()
の返す型がTimestamp
型でなかったり、そのままでは値の比較ができずDate型やミリ秒に変換する必要がある、などの罠もある。
4-5. Firestore のデータモデリング
- 歴史のあるRDBのように標準的な方法はまだ定まっていない。
- コレクションは階層化の親子関係がとれるが、むやみに使わない。
- IDはユニークに定義できるものがあればなるべく使い、ない場合のみFirestoreが自動生成するランダムなIDを使う。こちらは人間の目にわかりにくいため。
- 公開するセキュリティレベルが別だったらそれぞれ別のドキュメントにする。
- 重複を恐れずに非正規化する。
- 検索用のフラグなどを事前に持たせ、定期実行の
Cloud Function
で更新するなどして検索を工夫する。 1:1
のリレーションには{コレクション名}/{IDの値}
で表される「疑似リレーション」を両方のコレクションに持たせるのがよい。1:N
のリレーションにはふつうに外部IDをフィールドに持たせる。N:N
のリレーションでは前述のTrueマップを使う方法が健在。RDBならID同士の関係を持たせる別途のテーブルを用意するところだが、FirestoreだとJOINができない。
自分はRDBはよく使ってきたのでこういう話題になるとテーブル構造とかSQLが頭に浮かんでくるのですが、Firestoreだとけっこう独特で制限が多いですね…! 作中で秋谷さんが言っているように、気を付けないとハマるポイントがあちこちにありそうです。
4-6. Firestore だけで全文検索を実現する
- 全文検索の公式推奨は
Algolia
のような外部の検索サービスを使うこと。しかし高く連携設定が面倒。他にはElasticsearchを使う事例も。 - 方針として外部サービスを使わないでおきたい柴埼先輩は、なんと全文検索を自前で実装してしまった。
天才だ…天才がおる…!ということでこの4章の目玉はこの自前の全文検索エンジン実装です。簡単に言うと、
-単語の形態素解析にもいろいろあるがその中の一つ、Javaで開発されたOSSの日本語形態素解析エンジン Kuromoji
がある。これのJavaScript版のkuromoji.js
がnpmで公開されているのでこれを使う手もあるが、辞書ファイルが巨大になるデメリットあり。
- 形態素解析より簡単な、隣り合う文字に分解する
N-gram
(エヌグラム)の方法を柴埼先輩は採用。漫画の名前が「七つの大罪」だったら、JSのライブラリn-gram
を活用してドキュメントの登録時に名前を"七つ""つの""の大""大罪"
のように分解して、1ドキュメントごとにTrueマップで格納するようにする。 - 検索条件で「七つの大罪」が渡ってきたらこれも
N-gram
分解して配列に変換、先ほどのTrueマップと等価比較すると実質全文検索になる。 - ソートはできないのでReact側のクライアントソートで対処。
コード例も一緒に載っているのですが、よくこんなアイデア思いつくな……!と感嘆しました。作中の柴埼先輩も成功したとき自分で感動したと言っていますが、こういういろんな工夫を駆使して開発していくのですね。
第5章 React でフロントエンドを構築する
5-1. フロントエンド環境の整備
- 今回は状態管理の
Redux
は使わず、react-router
とCSSのEmotion
回りなどの最小構成。 - Firebaseとの接続に必要なAPIキー、アプリIDの値は管理コンソールから取得、プロジェクトルート直下の設定ファイル
.env
の中のREACT_APP_*
の項目に記述。
5-2. Context でグローバルな State を持つ
- src/index.tsx
<FirebaseApp>
でくるんだ中に<ThemeContext>
、その中に<App>
。CSS的なテーマなどのコンテキストは別ファイルで定義、React HooksのcreateContext()
で定義。 - src/FirebaseApp.tsx
firebase.firestore()
して取得したFirestoreオブジェクトを持ち、こちらもコンテキストで使いまわす。
5-3. Hooks で副作用処理を行う
- 発売カレンダーはコンポーネントにしているが、下のuseBooks()を呼ぶだけ。
- Custom Hookの
useBooks()
を定義。戻り値はオブジェクトにして本の配列、ローディング中か、エラーを持つ。useState()
を活用して本のリストなどは保持。useEffect()
の副作用を活用して初期表示時にFirestoreと接続、発行日が未来日のもうすぐ発売の本一覧を検索。
- もうひとつCustom Hookの
useBookSearch()
を定義。- こちらもFirestoreと接続して検索条件をもとに検索する。
useEffect()
の第2引数に検索条件のテキストボックスの値を入れているので、1文字でも変わった瞬間ごとにクエリーが走ってくれる。
- こちらもFirestoreと接続して検索条件をもとに検索する。
Reduxを使わない選択をした話で、柴埼先輩がHooks登場でReactが完全にいらない子になったのではなくて、「使わない選択を正しくできるようになった」のではないかと述べているのが興味深いです。習得も難しくコード量も増えるReduxで問題を解決しようとすると、解決できるけどオーバーキルになることが多かったとのこと。
確かに本章のReact Hooksの副作用を活用したコード、ぱっと見でもきれいに分割されてスッキリしている印象です。今後はこちらの路線が増えていくのかなあと思います。
第6章 Firebase Authentication によるユーザー認証
6-1. 認証機能を導入するための準備
- 説明を始めるんだけどもう柴埼先輩が先に作っちゃったといういつものムーブに、秋谷さんが慣れてきてだんだん投げやりになっていますw
- たくさんのSNSとやたら連携するのではなく、漫画文化と親和性が高いTwitterに限ったというのがサービス開発の見地に立っていて興味深いです。
- Twitter Developerのサイトにアクセスして
API Key
とAPI secret key
の2つを取得、Firebaseコンソール側に反映する手順がスクショ入りで詳しく解説されています。
6-2. ログイン機能の実装
createContext()
するファイルの中で、Firestoreオブジェクトのほかに認証情報、ユーザ情報も追加。- JSXで一番外側でくるんでいるsrc/FirebaseApp.tsx にログイン機能大幅追加。認証情報が変わったら書き換える処理。このコンポーネント内では
useState()
を使っているので中身が保持、さらに外側ではContextを使っているので、子供のコンポーネント群からもこのユーザ情報が参照できる。 - ログイン画面も別のコンポーネントとして実装。
react-firebaseui
というライブラリを使うと、JSXに所定のタグを書くだけでログインボタンの表示をやってくれる。
この整ったコードにたどり着くのに柴埼先輩はかなりハマったそうなので、その裏の作者さんもかなり試行錯誤されたのでは…と思います。
6-3. Firestore にセキュリティルールを適用する
- このままではAPIキーなどの値を抜き取ると
Mangarel
サービス用のFirestoreに悪意ある第三者もアクセスできてしまうのでセキュリティルールを適用する話。 - ブラウザでFirebaseコンソール上の設定画面から、プログラム言語のコードっぽい構文でさまざまに設定できる。
match /{コレクション名}/{ドキュメントのID項目}
の形でパス指定、1ドキュメント取得/ドキュメントのリスト取得/作成/更新/削除
を定義できる。- if文が書けるので、
users
コレクションのidが自分のものと等しい1ドキュメントしか更新不可、などが細かく定義できる。 - 作成時にユーザ名
null
不可、などのバリデーションルールもコードからかなり細かく設定可能。ドキュメント数、1ドキュメントの中のフィールド数や正規表現などなども使える。 - コンソールにもシュミレーターがある。
もうほとんどバリデーション処理をクライアントサイド/サーバサイドの何らかの言語でコードで書いているのと同じような感覚ですが、Firebaseはバックエンド不要のBaaSなのでこういうことも設定画面からできるわけですね。突き詰めるとかなり細かくいろんなことができそうです。
まとめ:Firebaseを使ったサーバーレス開発がわかる本
今回も内容はかなり濃く密度の高い本でした。設定ファイルのサンプルやコード例、問題解決の手法など作中では柴埼先輩が過去ハマったことをチラ見せしつつ割とさらっと正解を示していますが、その裏では作者さんはかなり試行錯誤されて調べながら実際のサービスまで完成させたのでは思います……
技術同人誌などでもFirebaseはキーワードとしてよく登場しますし、本書のあとがきでも「フロントエンドエンジニアをエンパワーする技術」だという一節があります。個人開発ベースやスモールスタートな事業での開発、バックエンドのエライい人やインフラを気にせず少人数でスピード優先で作っていくような開発に向いていそうですね。
一方Firebase自体については、他の書籍類でも見てきたのですがけっこうクセが強い技術だなと改めて思いました。これのよりRDBライクな版が出たら丸く収まりそうですが、そういう訳にもいかないんでしょうね……笑
今回も100ページちょっとですしお値段もお手軽、気軽に読める技術同人誌です。読んでみるとReact+Firebaseのサーバーレス開発のイメージが湧くのではないでしょうか。
なおネタバレですが最後の著者紹介のところに、今回はデスクツアー的に作者さんの執筆環境の机の写真が載っています。あっ確かにモニターの色合いが目に優しいSolarized Light
っぽい。そしてキーボードが2つに割れててトラックボールも左右に2つある! これ一体どうやって操作しとるんぢゃ……
関係書籍など
本書のサンプルコードのリポジトリ。
正誤表。 github.com
りあクト! TypeScriptで始めるつらくないReact開発 第3.1版【Ⅰ. 言語・環境編】 oukayuka.booth.pm りあクト! TypeScriptで始めるつらくないReact開発 第3.1版【Ⅱ. React基礎編】 oukayuka.booth.pm りあクト! TypeScriptで始めるつらくないReact開発 第3.1版【Ⅲ. React応用編】 oukayuka.booth.pm
りあクト! TypeScriptで極める現場のReact開発 booth.pm りあクト! Firebaseで始めるサーバーレスReact開発 booth.pm