FirestoreとGAEで無料でwebアプリをデプロイ

#この記事について
何かしらwebアプリをデプロイしたいけど個人で動かすと意外と料金がかかりがち。
GAEとFirestoreをうまく使ってwebアプリを無料でデプロイしてみる。

#きっかけ
GCPのGAE/Goで適当なアプリを作ってみようかなぁと思って、特になにも考えずにGo+Mysqlで動かそうとしていた。
ただGCPでデプロイしようとしたときCloudSQLを使うと、大したことないアプリのわりに無駄に高機能で高い料金になってしまう... (GCEも意外とかかる)

ってことでCloudSQLじゃなくて料金がやすそうなNoSQLで構築すればいいんじゃないの？と気づいた。(遅い)

##料金（無料枠）に関して
###GAE
- 公式
https://cloud.google.com/free/?hl=ja

1日あたりGAEのインスタンス28時間分が無料

要は1インスタンス24時間分＋スケールアウト用の4時間分ってこと
つまりGAEを1インスタンスで頑張れば無料で構築できます。

AWSとかと違って１２ヶ月だけじゃなくてずっと無料(Always Free)なんですよね...すごい...

###Firestore
- 公式
 https://firebase.google.com/pricing?hl=ja
![スクリーンショット 2019-12-11 16.33.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528040/7cf29892-34d7-a07d-7282-16d4b6188d21.png)

データ量1GB、1日2万回の更新まで無料

つまり公開したアプリのユーザアクセスが伸びないとずっと無料で使えます! (´；ω；｀)
個人でとりあえずアプリ公開したいってレベルなら十分じゃないでしょうか。

#ここから本題
GAEとFirestoreなら無料でいけそうとわかった。
ここからはGAE/GoでFirestoreを繋ぐための手順をまとめる

基本的には下記の公式ドキュメントに沿っていけばOK
https://firebase.google.com/docs/firestore/quickstart?hl=ja

ただし情報が飛び飛びなのとローカルからの接続方法になっています

##全体の流れ
GAE/GoでFirestoreを利用するための手順

1. GCPにGAEデプロイ用のプロジェクトを作る
2. FirebaseにFirestoreデータベースを作る
3. Firebase Admin SDKをインストールする 
4. Firestoreへのアクセスキーを作成する
5. アクセスキーを使ってローカル/GAEアプリからFirestoreへ接続する

##1.GCPにGAEデプロイ用のプロジェクトを作る
GCPの使い方に関しては調べればいくらでもあるのでこれは省略

##2.FirebaseにFirestoreデータベースを作る
1.Firebaseコンソールから、GCPと同じGoogleアカウントでサインインする
　https://console.firebase.google.com/

2.Firestoreのプロジェクト追加から、作成してあったGCPプロジェクトを追加
　その他設定はデフォルトでOK

3.開発リストのDatabaseからデータベースの作成を選択

4.テストモードで開始を選択
　ロケーションは自由に設定

これでFirestoreが作成されると思います。

ちなみに、FirestoreはFirebaseのFirestoreとGCPのFirestoreがありますが、両方同じものです。
プロジェクトの紐付けを行なっているとGCPコンソールからもFirebaseコンソールからも同じFirestoreを参照します。
(二つあるのは、Firebaseは昔は別サービスでGoogleが買収して後からGCPに統合したからとかそんな感じ。)

注意点として、FirebaseのサービスとしてFirestoreを単独で使う場合は無料のsparkプランを選べますが、GCPサービスと連携して使う場合は従量課金のBlazeプランになります。
Blazeプランでもspark分の無料枠はあるので使いすぎなければ問題ないです。
https://firebase.google.com/docs/storage/gcp-integration?hl=ja

##3.Firebase Admin SDKをインストールする
GoからFirebaseへアクセスするためのSDKをインストールします。
https://firebase.google.com/docs/admin/setup?hl=ja#initialize_the_sdk

```
$ go get firebase.google.com/go
```

もしかしたらライブラリの場所が問題でエラーが発生するかもしれないので
下記のようにgo modulesを設定しておいた方が無難だと思います

```
$ go mod init "アプリフォルダ名"
```

##4.Firestoreへのアクセスキーを作成する
インターネット経由でFirestoreにアクセスするためにFirebaseサービスアカウントキーを使います。

プロジェクトの 設定>サービスアカウント から秘密鍵を生成して、jsonファイルに保存しておきます。
(今回はsecret.jsonという名前で保存しています)

![スクリーンショット 2019-12-12 15.30.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528040/aaf0a36a-44c2-60d2-e169-a3a8e5eb36e6.png)


##5.ローカル/GAEからFirestoreへ接続する
ここまでで接続準備が完了しました。


あとはアプリからFirestoreへ接続するだけです。
GAEから接続する場合は、GAEにデプロイしてみてください。

ローカル実行でもGAE実行でもURLに接続するとFirestoreにデータが追加されていると思います。

![スクリーンショット 2019-12-12 15.37.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528040/cf586d68-db11-a1f6-fc04-46d2567cf3a5.png)

#まとめ
GAEとFirestoreを使えばある程度の規模まで無料で使える。
今ならCloudRunとかもリリースされてある程度無料で活用できるみたいですね。
何はともあれFirestore(Firebase)を使えるようになっておけば色々と便利そうです。


##サンプルソース


サンプルソースコード

```main.go

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"

	"cloud.google.com/go/firestore"
	"google.golang.org/api/option"
	"google.golang.org/appengine"
)

func main() {
	run()
	appengine.Main()
}

//リスナー起動
func run() {
	http.HandleFunc("/", indexHandler)
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
		log.Printf("Defaulting to port %s", port)
	}

	log.Printf("Listening on port %s", port)
	if err := http.ListenAndServe(":"+port, nil); err != nil {
		log.Fatal(err)
	}
}

//ハンドラー登録
func indexHandler(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}
	addFirestore()
	fmt.Fprint(w, "Added Firestore user !!")
}

//Firestoreへのデータ追加
func addFirestore() {
	ctx := context.Background()
	opt := option.WithCredentialsFile("secret.json")
	client, err := firestore.NewClient(ctx, "my-project-sample-qqq", opt)
	if err != nil {
		fmt.Println(err)
	}
	defer client.Close()

	//データ追加
	_, _, err = client.Collection("users").Add(ctx, map[string]interface{}{
		"first": "Ada",
		"last":  "Lovelace",
		"born":  1815,
	})
	if err != nil {
		log.Fatalf("Failed adding alovelace: %v", err)
	}

}

```
```secret.json
{
  "type": "service_account",
  "project_id": "my-project-sample-qqq",
〜〜〜
}
```