Goで実装してGoogle Cloud FunctionsからFirestoreへデータを追加する

#当記事の内容
Google Cloud FunctionsをGoで実装してHTTPリクエストを受け取りFirestoreへデータを追加する方法を記載します。

#開発環境
 - Google Cloud Shellを使用
 - 以下のコマンドを実施済

```shell
$ sudo gcloud components update && gcloud components install beta
```
 - Google Cloud Functions APIは有効化済
 - Go:1.11

#事前準備
##ネイティブモードを選択する
GCPではFirestoreを使う際に同一プロジェクト内でDatastoreモードとネイティブモードが選択できます。
Datastoreと互換性をもたせた形のFirestoreか、新しい形のFirestoreかの選択になります。
詳細は[こちら](https://cloud.google.com/datastore/docs/firestore-or-datastore?hl=ja)をご確認ください。

当記事ではネイティブモードを選択した場合の解説をしてまいります。

#実装とデプロイ
##Fucntionsプログラムの実装
Functionsで動作させるプログラムを作成していきます。
POSTリクエストが送られてきた際に"name"パラメータを取得してFirestoreへ出力しています。
GCPが提供している`"cloud.google.com/go/firestore"`ライブラリを用いてFirestoreへ出力しています。リクエストパラメータに加え現在時刻を付与しています。

```FunctionsToFirestore.go
package functions

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

        //Firestoreの操作に必要なライブ
        "cloud.google.com/go/firestore"
)

//Firestoreにデータを追加するための構造体、タグで変数とキーを紐づける
type Data struct {
        Name     string    `firestore:"NAME"`
        Datetime time.Time `firestore:"DATETIME"`
}

//HTTPトリガーで実行される
func FunctionsToFirestore(w http.ResponseWriter, r *http.Request) {
        switch r.Method {
        case http.MethodPost: //POSTの場合

                //パラメータの"name"から値を取り出す
                name := r.PostFormValue("name")

                //取り出せない場合はエラーとして処理を終了する
                if name == "" {
                        fmt.Fprint(w, "パラメータに\"name\"がありません。\r\n")
                        return
                }

                //Firestoreへ出力する関数を呼び出す
                CreateFirestore(name)

        default: //POST以外の場合はエラー
                http.Error(w, "405 - Method Not Allowed", http.StatusMethodNotAllowed)
        }
}


func CreateFirestore(name string) {

        //流し込むデータを構造へ格納する
        data := Data{}
        data.Name = name
        data.Datetime = time.Now()

        //コンテキストを取得する
        ctx := context.Background()

        //プロジェクトIDを取得する
        projectID := os.Getenv("GCP_PROJECT")

        //Firestoreを操作するクライアントを作成する、エラーの場合はLoggingへ出力する
        client, err := firestore.NewClient(ctx, projectID)
        if err != nil {
                log.Printf("Firestore接続エラー　Error:%T message: %v", err, err)
                return
        }

        //確実にクライアントを閉じるようにする
        defer client.Close()

        //現在時刻を構造体へ格納する
        data.Datetime = time.Now()

        //Firestoreの追加を行う、エラーの場合はLoggingへ出力する
        _, _, err = client.Collection("NAMES").Add(ctx, data)
        if err != nil {
                log.Printf("データ書き込みエラー　Error:%T message: %v", err, err)
                return
        }
}
```


##モジュールの用意
次にgo.modを準備します。`"cloud.google.com/go/firestore"`を使用しているので、必要なライブラリに追加します。バージョンは0.36.0[^1]を指定します。

[^1]: 2019/4/1現在0.37.1が最新ですが。Go1.12が必要になるためGoogle Cloud Functionsでは動きません。


```go.mod
module functions

go 1.11

require cloud.google.com/go v0.36.0
```

##デプロイ
プログラムの準備が出来たらデプロイをしていきます。
`gcloud functions deploy`コマンドを実行すると3分くらいでデプロイが完了します。

```shell-session
$ gcloud functions deploy FunctionsToFirestore --runtime go111 --trigger-http 
（略）
status: ACTIVE
timeout: 60s
（略）
```

#テスト
`curl`で実際にGETリクエストを送信してみます。リクエストを送るエンドポイントはデプロイした時のログに出ていますので探します。

```shell-session
$ curl https://us-central1-YOUR_PROJECT.cloudfunctions.net/FunctionsToFirestore -X POST -d "name=aaaa"
```

次にWEBコンソールからデータが追加されたかの確認をします。WEBコンソールのメニューからFirestoreを選択します。最上位の```"NAMES"```というコレクションが出来上がっていて、その中にユニークIDでコレクションが作られています。中のフィールドにPOSTで送ったパラメータと実行時刻が入っていればテスト完了です。
![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/168941/26aaba99-fea7-6b03-4fd8-4ba308156b01.png)

#最後に
簡単にFirestoreへ書き込むREST APIが作成できました。Firestoreは比較的新しい機能だからか```gcloud```コマンドでストアされてる中身を確認することができません。今後の機能の充実に期待です。

2019/4/14の技術書典６に`"Goで学ぶGoogleCloudFunctions"`という本を頒布します。興味ある人は是非是非おこしください。[サークルサイトはこちらです。](https://techbookfest.org/event/tbf06/circle/34050001)