CloudFunctionsでFireStoreにドキュメントを追加してみる

#今回すること
最近無人島生活で忙しいRHEMS技研の葛西です。

今回はGCPのCloudFunctionsでFireStoreにドキュメントを追加する方法を紹介していきたいと思います。
また、DataFlow+BigQueryとCloudFunctions+FireStoreでのコストの比較も行います。

#構成図
![構成図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285891/f2c75570-90d4-5a8f-c52c-893f006e82dc.png)

今回はCloudFunctionsとFireStoreについての記事なので、IoTCore等の詳細は[こちら](https://qiita.com/Akijin/items/c060a416b9d5e0b52de8)をご覧ください。

#FireStoreの準備
CloudFunctionsで関数を作る前に、先にデータの受け入れ先の準備をします。
都合上プロジェクトがバラバラになってしまっていますが、CloudFunctionsとFireStoreは**同じプロジェクト**で作業してください。
FireBaseのコンソールを開き、左のメニューから「Database」を選択します。

<img width="1614" alt="スクリーンショット 2020-04-04 16.38.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285891/c66203cf-893a-6634-b8d8-bcce547f77d5.png">

「データベースの作成」をクリックします。

<img width="837" alt="スクリーンショット 2020-04-04 16.40.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285891/fbb26d5f-f6f6-1c6f-fa1d-5b93defe2eb9.png">

今回はテストモードで進めていきます。

<img width="831" alt="スクリーンショット 2020-04-06 17.07.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285891/2f9bcfe4-896e-e6a2-b79d-8995f9e09231.png">

ロケーションは**asia-northeast1**(東京)にします。(どこでも特に問題はないハズですが、CloudFunctionsのロケーションと合わせたほうがいいと思います。)

データベースの作成が終わるとこのような画面になります。
<img width="1363" alt="スクリーンショット 2020-04-06 17.08.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285891/a5ccb737-9a70-53f2-535b-72ecfee502c9.png">

##コレクションの作成
それではコレクションを開始を押して、コレクション(SQLで言うテーブルのようなもの)を作っていきます。

<img width="708" alt="スクリーンショット 2020-04-04 16.48.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285891/f81d6836-8d3a-c82e-bce4-0da9d3a262b6.png">

適切なIDを付けましょう。今回は「SuperCollection」にしました。
<img width="711" alt="スクリーンショット 2020-04-04 16.54.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285891/6ced9ddf-0355-ce2d-ff43-9d396882bbd5.png">

上のように最初のドキュメントを追加したら、保存をクリックします。
<img width="1361" alt="スクリーンショット 2020-04-06 17.09.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285891/9783fb94-bbdc-ee2f-928e-5bea76beeb9e.png">

この様になっていればOKです。

##サービスアカウントの用意
CloudFunctionsからFireStoreにドキュメントを追加するために、秘密鍵が必要になるのでそれの準備をしていきます。
<img width="701" alt="スクリーンショット 2020-04-06 17.10.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285891/0ccb85f2-54ec-1836-3d53-66ab44221ff1.png">

左上の歯車マークをクリックし、「ユーザーと権限」をクリックします。

画面上部の「サービスアカウント」をクリックし、「新しい秘密鍵の生成」をクリックします。
<img width="1615" alt="スクリーンショット 2020-04-04 17.11.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285891/6a33e2df-edc5-cbfc-e422-65215fc55163.png">
「キーを生成」をクリックして、jsonがダウンロードされれば完了です。

#CloudFunctionsの作成
次にCloudFunctionsで関数を作ります。
左上のナビゲーションメニューからCloudFunctionsをクリックします。
すると、初回の場合は下のような画面になるので、「関数を作成」をクリックします。<img width="1680" alt="スクリーンショット 2020-04-04 15.56.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285891/1a377059-28e8-1340-176f-403f32c58f0d.png">
今回はCloud Pub/Subから受け取ったデータをFireStoreに入れるため、トリガーをCloud Pub/Subにします。
トピックは、今回作成する関数でFireStoreに追加したいデータを受け取れるものを選択してください。
<img width="563" alt="スクリーンショット 2020-04-04 15.58.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285891/6046af7e-90df-41d7-208f-937d4baca7e4.png">
<img width="610" alt="スクリーンショット 2020-04-04 16.26.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285891/5521eb3c-54cd-1f31-59c4-fb4f8bed82e6.png">

今回はインラインエディタを使って編集していきます。
送られてくるデータの形式は

```json
{"Hello":"FireStore"}
```

とします。

```js:index.js
/**
 * Triggered from a message on a Cloud Pub/Sub topic.
 *
 * @param {!Object} event Event payload.
 * @param {!Object} context Metadata for the event.
 */
const admin = require('firebase-admin');

const serviceAccount = 先程ダウンロードしたjsonを貼り付ける

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount)
});

var db = admin.firestore();

exports.helloPubSub = (event, context) => {
  const pubsubMessage = event.data;
  const raw = Buffer.from(pubsubMessage, 'base64').toString();
  console.log(raw);
  const obj = JSON.parse(raw);

  let docRef = db.collection('SuperCollection').add({
    Hello: obj.Hello
  }).then(function (docRef) {
    console.log("Document written with ID :", docRef.id);
  }).catch(function (error) {
    console.log("Error :", error);
  });

  console.log("Hello :",obj.Hello);
};
```

firebase-adminを使うために、Package.jsonも編集します。

```json:Package.json
{
  "name": "sample-pubsub",
  "version": "0.0.1",
  "dependencies": {
    "@google-cloud/pubsub": "^0.18.0",
    "firebase-admin": "^5.10.0"
  }
}
```

このように変更します。

「作成」をクリックし、デプロイが完了すればOKです。

あとは　ESP32からのデータを受信するごとにFireStoreのコレクションにドキュメントが追加されていきます。

#コストの比較

Dataflow+BigQueryを使用した場合とCloudFunctions+FireStoreを使用した場合でコストの比較をしていきます。
今回の計算では1分に1回（1時間に60回）データを送信することとします。
また、IoTCoreとCloud Pub/Subは今回の計算の場合、データ量が少なく無料枠の範囲内なので省略します。

##DataFlow+BigQuery

###DataFlow
DataFlowにはバッチ処理とストリーミング処理がありますが、今回はストリーミング処理の方で計算していきます。
デフォルトのマシンほどのスペックはいらないので、e2-medium(vCPU:2 メモリ:4GB)の場合、ストレージは100GBで計算します。

1時間あたり約0.206米ドルなので、0.206*24*30=148.32
(1時間あたりの計算内訳 vCPU:0.069*2 メモリ:0.003557*4 ストレージ:0.000054*100)

1ヶ月あたり約**148.32米ドル**になります。かなり高い。

###BigQuery

BigQueryに関しては、定額制ではない場合は月に行うクエリの量で料金が大きく変動するので、詳細に関しては[公式のドキュメント](https://cloud.google.com/bigquery/pricing)をご参照ください。

DataFlow+BigQueryの場合、最低でも148.32米ドルかかることになります。

##CloudFunctions+FireStore

###CloudFunctions

1時間あたり60回関数が呼び出されるので、1ヶ月あたりの呼び出し回数は60*24*30=43200回となります。

CloudFunctionsは、1ヶ月あたり最初の200万回は無料なので、CloudFunctionsでは**料金はかかりません。**

###FireStore

1回呼び出される毎に1つのドキュメントが書き込まれるので、1日あたり1440個のドキュメントがコレクションに追加されることになります。

FireStoreは、1日あたり、2万件までの書き込みが無料なので、こちらも**料金がかかりません。**

CloudFunctions+FireStoreの場合、今回の計算の場合は無料枠の範囲内で済ませることができます。すごい

#おわりに
今回はCloudFunctionsでFireStoreにドキュメントを追加する方法についてまとめました。
GCPを触ったのは今回が初めてなので、間違っている点、もっとよくできる点等ありましたらご指摘ください。

サービスの組み合わせ次第でかなり節約できることがあるので、みなさんも組み合わせの見直しをしてみると費用を節約できるかもしれませんのでぜひ確認してみてください！