Oh my Buddha!!

主にWeb界隈のえんじにありんぐ的なことについて

表参道.rb で、技術書典5で出す本の宣伝と Firebase について話してきました!

speakerdeck.com

発表した資料です。

どんなイベントだった?

表参道.rb というイベントで、今回は技術書典がテーマだったのですが、Ruby じゃなくても、全然OKな雰囲気の会でした!

omotesandorb.connpass.com

ちなみに、今回の会場が、僕の前職の会社でして、しかも参加者の中には、僕を含めて、他にも元社員の人が3人いました。

(そのうち一人は、僕が、新卒1年目に同じチームで働いていた1つ上の新卒2年目の先輩で、懐かしみが深かったです。久しぶりに会えてとても楽しかった)

ちょっとしたOB会です😅

LTで話し足りなかったこと

LTでは、Firebase を使ってみた感想をざっくり話したのですが、その中で、

Firestore は、NoSQLデータベースであるため、JOINは使えず、RDBと同じようなDB設計ができない

と言っていたので、この点について、このブログ記事でもう少し補足してみようかと思います。

Firestore におけるDB設計

DB設計について考える場合、そもそもFirestoreのようなドキュメント指向のNoSQLデータベースではどんなDB設計をするべきなのか、いろいろと情報を漁ってみました。

以下は、いろいろ調べた中で、個人的に参考になった記事です。

NoSQLデータモデリング技法

NoSQLデータモデリング技法 · GitHub

参考になった点

  • Firestore に限定されず、NoSQLデータベースとしてのプラクティスが網羅されている
  • いきなり具体例ではなく、概念から解説してから、具体的な技法について解説している
  • RDBでは、基本的には、正規化を徹底するが、NoSQLデータベースでは、非正規化を許容する、むしろ活かす

Firestore Database Design

speakerdeck.com

参考になった点

  • Firestore にフォーカスしている
  • Firestore の基本的なデータモデルの概念であるコレクションとドキュメント・サブコレクションについて、図だけで十分伝わる
  • Firestore を使った場合の開発の特徴(Client Side Join) についての言及している
  • リレーションを Firestore ではどのように表現するか具体的なクエリ共に解説されている
  • セキュリティルールについて取り上げている
  • データの冗長化について取り上げている

Cloud Firestoreの勘所 パート2 — データ設計

medium.com

参考になった点

  • ブログサービスというかなり具体的な例を用いて、設計について解説しているのが超絶わかりやすかった
  • 上2つの記事を読んで、事前情報を知った上で、再度読むとより理解が深まった

Get to know Cloud Firestore

www.youtube.com

最後にFirebase公式のYouTubeチャンネルです。

参考になった点

  • 安定安心の公式の情報源というだけでなく、RDBと比較して、Firestore の解説をしている
  • 全て英語ですが、字幕もあり、動画内で説明用に出る図もわかりやすい

※ 全然話が関係ないのですが、動画で話している人が、ドラマMr. Robot の主人公の親父役の俳優にそっくりだと思うのは僕だけでしょうか?

https://cdn-ak.f.st-hatena.com/images/fotolife/A/Annice/20170722/20170722081449.jpg

おまけ

同サークルのメンバーが、イケてるおしながきを作ってくれたので、ちょっと宣伝✨

Firebase や Flutter に興味がある人はぜひ立ち寄ってみて下さ〜い☺️

https://techbookfest.org/event/tbf05/circle/47080001

f:id:wakuwakuozisan:20181005014238p:plain

技術書典5で、Firebaseについての本を出します!

どんな本を出すのか?

Firebase についての本を出します。

表紙は、こんな感じです。

f:id:wakuwakuozisan:20180927233012j:plain

表紙のデザイン自体は、Canva というデザインポートフォリオ作成サイトで、作成しました。

入稿先

  • 日光企画という技術書典と提携している印刷所に申し込みました。

日光企画がよいところ

  • 同人誌作成は、今回が初めてだったのですが、丁寧に説明していただいたのが、好感を受けました
  • 技術書典の会場に直接搬入が可能
  • 印刷代は、技術書典割引なるものがあり、多少印刷代が安くなります。今回僕は、74ページの本を100部オフセット印刷することになり、合計で、¥52,040 でした。

どんなツールを使ったか

  • Gitbook を使いました。大後悔です。ReViewで作るべきでした。。。

Gitbook がイケてないところ・イケてるところ

イケてないところ

  • PDF出力したときに、謎の空白ページができる
  • ページ番号の表示を設定で、OFFにしても反映されない
  • シンタックスハイライトのPDF出力が少し荒れる

イケてるところ

  • 手軽。以上。

出典サークル

サークル名は、茶しぶ です! 場所は、う69 です。よろしくおねがいします!

techbookfest.org

【Firebase】Cloud Functionsで学ぶPromiseとasync/await

はじめに

Cloud Functions for Firebase の学習(動画シリーズ)が、Cloud Functions入門としてだけでなく、Promiseasync/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を返す
  • 対応言語は、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の特徴

  1. 常にPromiseを返す
  2. asyncを付けた関数は、Promiseを返す処理でなかったとしても、元々の返り値をPromise型にラップして、返す
  3. awaitを付けた関数は、必ずPromiseが完了(resolve)した値を返す
  4. try~catch構文を使用して、途中で失敗したawait関数のエラーを補足できる
  5. 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)
-    })
  })

またボストンだけでなく、他の地域も並列で取得し、全地域の天気情報が取得できたら、何かしらのレスポンスを返す関数も作成していました。 現状の関数は、以下のとおりです。 複数のthenPromise.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記事の方にも書きました。

qiita.com

ブログ始めます!!

動機

  • TwitterFacebookではおさまりきらない考えや気になる技術の調査ログを残していきたい
  • セルフブランディングしていかんとなあと気分が高まったため

自己紹介

  • 東京のとあるWeb企業で、エンジニアをやっています
  • 男(20代)
  • りんごと梅干しとトマトが好き。つまり赤い食べ物全般好き

書きたいこと

  • 技術ネタ
  • 日々の暮らしで気になったこと
  • ギークハウスのこととか