表参道.rb で、技術書典5で出す本の宣伝と Firebase について話してきました!
発表した資料です。
どんなイベントだった?
表参道.rb というイベントで、今回は技術書典がテーマだったのですが、Ruby じゃなくても、全然OKな雰囲気の会でした!
ちなみに、今回の会場が、僕の前職の会社でして、しかも参加者の中には、僕を含めて、他にも元社員の人が3人いました。
(そのうち一人は、僕が、新卒1年目に同じチームで働いていた1つ上の新卒2年目の先輩で、懐かしみが深かったです。久しぶりに会えてとても楽しかった)
ちょっとしたOB会です😅
元社員多いなw #omotesandorb
— sue445@10/8技術書典5 か75 (@sue445) October 4, 2018
OB会っぽい #omotesandorb
— はいと (@HaiTo_Linux) October 4, 2018
みんなでわいわい( ´∀`)#omotesandorb #元Sansan多い #なんか嬉しい pic.twitter.com/lOZkCpPQ8u
— ふじこ (@yu_fujikochan) 2018年10月4日
LTで話し足りなかったこと
LTでは、Firebase を使ってみた感想をざっくり話したのですが、その中で、
Firestore は、NoSQLデータベースであるため、
JOIN
は使えず、RDBと同じようなDB設計ができない
と言っていたので、この点について、このブログ記事でもう少し補足してみようかと思います。
Firestore におけるDB設計
DB設計について考える場合、そもそもFirestoreのようなドキュメント指向のNoSQLデータベースではどんなDB設計をするべきなのか、いろいろと情報を漁ってみました。
以下は、いろいろ調べた中で、個人的に参考になった記事です。
NoSQLデータモデリング技法
参考になった点
- Firestore に限定されず、NoSQLデータベースとしてのプラクティスが網羅されている
- いきなり具体例ではなく、概念から解説してから、具体的な技法について解説している
- RDBでは、基本的には、正規化を徹底するが、NoSQLデータベースでは、非正規化を許容する、むしろ活かす
Firestore Database Design
参考になった点
- Firestore にフォーカスしている
- Firestore の基本的なデータモデルの概念であるコレクションとドキュメント・サブコレクションについて、図だけで十分伝わる
- Firestore を使った場合の開発の特徴(Client Side Join) についての言及している
- リレーションを Firestore ではどのように表現するか具体的なクエリ共に解説されている
- セキュリティルールについて取り上げている
- データの冗長化について取り上げている
Cloud Firestoreの勘所 パート2 — データ設計
参考になった点
- ブログサービスというかなり具体的な例を用いて、設計について解説しているのが超絶わかりやすかった
- 上2つの記事を読んで、事前情報を知った上で、再度読むとより理解が深まった
Get to know Cloud Firestore
最後にFirebase公式のYouTubeチャンネルです。
参考になった点
- 安定安心の公式の情報源というだけでなく、RDBと比較して、Firestore の解説をしている
- 全て英語ですが、字幕もあり、動画内で説明用に出る図もわかりやすい
※ 全然話が関係ないのですが、動画で話している人が、ドラマMr. Robot の主人公の親父役の俳優にそっくりだと思うのは僕だけでしょうか?
おまけ
同サークルのメンバーが、イケてるおしながきを作ってくれたので、ちょっと宣伝✨
Firebase や Flutter に興味がある人はぜひ立ち寄ってみて下さ〜い☺️
https://techbookfest.org/event/tbf05/circle/47080001
技術書典5で、Firebaseについての本を出します!
どんな本を出すのか?
Firebase についての本を出します。
表紙は、こんな感じです。
表紙のデザイン自体は、Canva というデザインポートフォリオ作成サイトで、作成しました。
入稿先
- 日光企画という技術書典と提携している印刷所に申し込みました。
日光企画がよいところ
- 同人誌作成は、今回が初めてだったのですが、丁寧に説明していただいたのが、好感を受けました
- 技術書典の会場に直接搬入が可能
- 印刷代は、技術書典割引なるものがあり、多少印刷代が安くなります。今回僕は、74ページの本を100部オフセット印刷することになり、合計で、¥52,040 でした。
どんなツールを使ったか
- Gitbook を使いました。大後悔です。ReViewで作るべきでした。。。
Gitbook がイケてないところ・イケてるところ
イケてないところ
- PDF出力したときに、謎の空白ページができる
- ページ番号の表示を設定で、OFFにしても反映されない
- シンタックスハイライトのPDF出力が少し荒れる
イケてるところ
- 手軽。以上。
出典サークル
サークル名は、茶しぶ です! 場所は、う69 です。よろしくおねがいします!
【Firebase】Cloud Functionsで学ぶPromiseとasync/await
はじめに
Cloud Functions for Firebase の学習(動画シリーズ)が、Cloud Functions入門としてだけでなく、Promiseやasync/await構文の解説としても、とてもわかりやすかったので、メモとして残すことにしました。
セットアップ
Firebaseコンソールからプロジェクトを事前に作成
- https://console.firebase.google.com/ にアクセスして、「プロジェクトの追加」をクリックして、プロジェクトを作成
- 今回の記事で使用するプロジェクト名は、
cloud_functions_sample
にします
CLIからの操作
- Node.js v6以上
- npm(v5.6.0以上)
mkdir cloud_functions_sample cd cloud_functions_sample npm i -g firebase-tools # Googleアカウントでの認証が要求されるので、許可しましょう firebase login # プロジェクトの選択やランタイム # 途中、どの機能を使用するか聞かれるので、Cloud Functionsのみを矢印キーとスペースキーで選択して、Enter firebase init
Cloud Functions
のランタイムは、今回は、TypeScript
を使用します。
理由としては、async/await
構文を使用したいからですが、TypeScript以外のランタイムは、Node.jsのみで、そのバージョンが、 v6.11.5 のため、async/await
が使用できません。
コマンド処理が完了すると、プロジェクトのルートディレクトリ以下に、functions
というディレクトリができているので、functions
ディレクトリ内にあるindex.ts
デプロイ
- 実行したい関数ができたとき、
Cloud Functions
のみデプロイしたい場合は、以下コマンドを実行
firebase deploy --only functions
今回は、TypeScriptを使用しているため、通常なら素のJavaScriptにコンパイルする必要がありますが、Firebaseは、デプロイ時にそのへんもよしなにやってくれます。やったぜ😉
主な特徴
- イベントトリガーで、バックグラウンドに実行
- HTTPによるトリガー
- レスポンスを返す
- バックグラウンドによるトリガー
- Promiseを返す
- HTTPによるトリガー
- 対応言語は、JavaScript, TypeScript, Node.js
- TypeScriptとVS Codeにすると、FirebaseのAPIのレスポンスの型が補完で出てくるので、便利
- レスポンスの型は基本的にPromise
- HTTPSでのみ動作可能
イベントトリガーの種類
HTTPトリガー
functions.https.onRequest
の部分
例:とあるエンドポイントURLにアクセスした時に実行
import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' export const getBostonWeather = functions.https.onRequest((req, res) => { // something action })
バックグラウンドトリガー
- データベース(
Firestore
,Realtime Database
)のいずれかのCRUD処理をトリガーに自動実行 - 他にも、
Cloud Storage
へのアップロードをトリガーに実行できるなど、イベントトリガーの種類はいくつかある- 詳細は、こちら
例:Firestoreの更新時に実行
export const onBostonWeatherUpdate = functions.firestore.document("cities-weather/boston-ma-us").onUpdate(change => { // something action })
Cloud FunctionsにおけるPromiseによる逐次処理と並列処理
ユースケース
- ボストンだけでなく、他の地域の天気を取得したり、更新したいとき
- Firestoreのデータ構造
- areas - greater-boston - cities - bostom-ma-us: true - cambridge-ma-us: true - somerville-ma-us: true
- それぞれの地域のデータ取得・更新は、並列処理したい
- HTTPトリガーなので、並列処理で全ての処理を終えてから1つのレスポンスを返すようにしたい
- 並列処理を実現するためには、
Promise.all
を使う
export const getBostonAreaWeather = functions.https.onRequest((request, response) => { admin.firestore().doc("area/greater-boston").get() .then(areaSnapshot => { const cities = areaSnapshot.data().cities const promises = [] for (const city in cities) { const p = admin.firestore().doc(`cities-weather/${city}`).get() promises.push(p) } return Promise.all(promises) }) .then(citySnapshots => { const results = [] citySnapshots.forEach(citySnap => { const data = citySnap.data() data.city = citySnap.id results.push(data) }) response.send(results) }) .catch(error => { console.log(error) response.status(500).send(error) }) })
結果
[ { "city": "somerville-ma-us", "conditions": "partly-cloudy", "temp": 8 }, { "city": "boston-ma-us", "conditions": "sunny", "temp": 10 }, { "city": "cambridge-ma-us", "conditions": "sunny", "temp": 9 } ]
ここまでが、Promiseを使った非同期処理となる
これを後述するasync/await
を使った処理だと、より同期的な見た目で非同期処理を書くことができる
イメージ
const p1 = doSomeWork(1) const p2 = doSomeWork(2) const p3 = doSomeWork(3) const pMany = [p1, p2, p3] const finalPromise = Promise.all(pMany) finalPromise.then(results => { results.forEach(result => {...}) }) .catch(error => {...})
async/await
によるリファクタリング
例
async function myFunction(): Promise<any> { try { // const rankPromise = getRank() const rank = await getRank() return "firebase" } catch (err) { return "Error: " + err } } functiongetRank() { return Promise.resolve(1) }
async/await
の特徴
- 常にPromiseを返す
async
を付けた関数は、Promiseを返す処理でなかったとしても、元々の返り値をPromise型にラップして、返すawait
を付けた関数は、必ずPromiseが完了(resolve)した値を返すtry~catch
構文を使用して、途中で失敗したawait
関数のエラーを補足できるawait
キーワードは、async
関数内でしか、使用できない
async/await
で、Cloud Functions内のコードを書き換える
先程、ボストンの天気を取得する関数を作成したので、それを書き換えます。 今の実装は以下の通り。
import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' admin.initializeApp() export const getBostonWeather = functions.https.onRequest((request, response) => { admin.firestore().doc("cities-weather/boston-ma-us").get() .then(snapshot => { const data = snapshot.data() response.send(data) }) .catch(error => { console.log(error) response.status(500).send(error) }) })
これをasync/await
構文をを使用して、以下のように変更
import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' admin.initializeApp() export const getBostonWeather = + functions.https.onRequest(async (request, response) => { + try { + const snapshot = await admin.firestore().doc("cities-weather/boston-ma-us").get() + const data = snapshot + response.send(data) + } + catch (error) { + console.log(error) + response.status(500).send(error) + } - admin.firestore().doc("cities-weather/boston-ma-us").get() - .then(snapshot => { - const data = snapshot.data() - response.send(data) - }) - .catch(error => { - console.log(error) - response.status(500).send(error) - }) })
またボストンだけでなく、他の地域も並列で取得し、全地域の天気情報が取得できたら、何かしらのレスポンスを返す関数も作成していました。
現状の関数は、以下のとおりです。
複数のthen
とPromise.all
を使って、それぞれの地域の天気情報を並列で取得し、更新していました。
export const getBostonAreaWeather = functions.https.onRequest((request, response) => { admin.firestore().doc("area/greater-boston").get() .then(areaSnapshot => { const cities = areaSnapshot.data().cities const promises = [] for (const city in cities) { const p = admin.firestore().doc(`cities-weather/${city}`).get() promises.push(p) } return Promise.all(promises) }) .then(citySnapshots => { const results = [] citySnapshots.forEach(citySnap => { const data = citySnap.data() data.city = citySnap.id results.push(data) }) response.send(results) }) .catch(error => { console.log(error) response.status(500).send(error) }) })
async/await
に書き換えたのが以下のとおりです。
export const getBostonAreaWeather = + functions.https.onRequest(async (request, response) => { + try { + admin.firestore().doc("area/greater-boston").get() + const cities = areaSnapshot.data().cities + const promises = [] + cities.forEach(city => { + const p = admin.firestore().doc(`cities-weather/${city}`).get() + promises.push(p) + }) + const snapshots = await Promise.all(promises) + + const results = [] + citySnapshots.forEach(citySnap => { + const data = citySnap.data() + data.city = citySnap.id + results.push(data) + }) + response.send(results) + } + catch (error) { + console.log(error) + response.status(500).send(error) + } - admin.firestore().doc("area/greater-boston").get() - .then(areaSnapshot => { - const cities = areaSnapshot.data().cities - const promises = [] - for (const city in cities) { - const p = admin.firestore().doc(`cities-weather/${city}`).get() - promises.push(p) - } - return Promise.all(promises) - }) - .then(citySnapshots => { - const results = [] - citySnapshots.forEach(citySnap => { - const data = citySnap.data() - data.city = citySnap.id - results.push(data) - }) - response.send(results) - }) - .catch(error => { - console.log(error) - response.status(500).send(error) - }) - })
const snapshots = await Promise.all(promises)
に変更することによぅて、Promise.all
として完了した値を担保してくれます。
async/await
を使うことで、then
式が不要になり、同期的なシンタックスが実現でき、可読性が上がります。
一応、Qiita記事の方にも書きました。