AppEngine Go でCloud Firestoreを使う

#AppEngine GoでCloud Firestoreを使う
こんにちは、今回はFireBaseのCloud FirestoreをAppEngine Goから使う方法を紹介しようと思います。
AppEngineからの使い方を紹介している記事は調べた感じなさそうだったので自分のメモを兼ねてまとめていきます。

##はじめに
**ご注意:
プロジェクトの課金を有効化しないとデプロイ後の動作でCloud FirestoreとのSocket通信時にエラーとなります。課金を有効化してからお試し下さい。** (2018/10/17 追記)

AppEngineでFireStoreを使う場合、AppEngineインスタンスの作成よりも先にFireBaseプロジェクトを作成してFireStoreを有効化、GCPプロジェクトとの紐付けを行って下さい。
**Cloud DatastoreとFireStoreは同一プロジェクトに共存できない**ため先にFireStoreを有効化しないとDatastoreしか使えません。(2018/06/21現在)

FireBaseプロジェクトの作成は下記コンソール(WEB)から
[FireBaseConsole](https://console.firebase.google.com/u/0/?hl=ja)

今回はFireStoreをAppEngine Goから使うことを目的としていますので、まだAppEngine Goの環境を整えていない方は以前書いた**[GAE Go 開発環境の構築からテストアプリのデプロイまで【MacOS】](https://qiita.com/IJN-Penguin/items/782a23662d68aab1316c)**を御覧ください。**※Mac OS以外の方は各自検索**

環境構築が終わりましたらGoのプロジェクトフォルダの構成までお願いします。
参考までに構成について書いた記事は**[こちら](https://qiita.com/IJN-Penguin/items/4e053eb0dd0a433b4a14)**です。

##ソースコード
開発する環境の構築とフォルダの構成が完了したところで、早速コードを書いていこうと思います。
基本的には[FireBase公式のドキュメント](https://firebase.google.com/docs/firestore/quickstart?hl=ja)を参考に行いますが、AppEngineについてのドキュメントではないのでご注意下さい。

###Cloud Firestore を初期化する
公式ドキュメントの**Google Cloud Platform で初期化する**を参考に行いますが、早速ここでAppEngineと通常のGoでは違いが出てきます。

```go:app.go
import (
	firebase "firebase.google.com/go"
	"google.golang.org/api/iterator"
	"google.golang.org/appengine"
	"google.golang.org/appengine/log"
)
```

```go:app.go

ctx := appengine.NewContext(r) // rはHandlerの引数 *http.Request

projectID := "YOUR-GCP-PROJECT-ID" //本番では直書きしないほうがいい
conf := &firebase.Config{ProjectID: projectID}
app, err := firebase.NewApp(ctx, conf) // ここでAppEngineのコンテキストが使えます

if err != nil {
	panic(err)
}

client, err := app.Firestore(ctx)

if err != nil {
	panic(err)
}

defer client.Close()
```
ここで注目すべきはAppEngineのコンテキストをそのまま使える点です。
公式ドキュメントには書いていないため実際にコードを書いてみるまでわかりませんでした。
恐らくここでハマる人が多いのではないでしょうか？
ここさえクリアできれば後は公式ドキュメント通りですので非常に楽です。

この記事での目的はAppEngine GoからFireStoreを使うことですので以下はあまり詳しく書きません。
公式ドキュメントを読んで進めて下さい。

###データの追加
このあたりは公式のドキュメントそのままです

```go:app.go
_, _, err = client.Collection("users").Add(ctx, map[string]interface{}{
	"first": "Ada", // key:valueで追加できます
	"last":  "Lovelace",
	"born":  1815,
})

if err != nil {
	log.Debugf(ctx, "Failed adding alovelace: %v", err)
}
```

###データを読み取る
こちらも公式ドキュメントそのまま

```go:app.go
iter := client.Collection("users").Documents(ctx)
for {
	doc, err := iter.Next()

	if err == iterator.Done {
		break
	}

	if err != nil {
		log.Debugf(ctx, "Failed to iterate: %v", err)
	}

	fmt.Println(doc.Data()) // この部分はAppEngineLogなりFprintなりに変更
}
```

###クエリ
クエリは.Where("key", "オペレータ", 値)で行えます。
.Where("name", "==", name).Where("age", "==", age)などつなげることも可能です。

```go:app.go
name := "Jack"

iter := client.Collection("users").Where("name", "==", name).Documents(ctx)
for {
	doc, err := iter.Next()

	if err == iterator.Done {
		break
	}

	if err != nil {
		log.Debugf(ctx, "Failed to iterate: %v", err)
	}

	fmt.Println(doc.Data()) // この部分はAppEngineLogなりFprintなりに変更
}
```

###ドキュメントを削除する
[データ削除に関する公式ドキュメント](https://firebase.google.com/docs/firestore/manage-data/delete-data?hl=ja)

```go:app.go
_, err := client.Collection("cities").Doc("DC").Delete(ctx)

if err != nil {
	return err
}
```

###フィールドを削除する

```go:app.go
_, err := client.Collection("cities").Doc("BJ").Update(ctx, []firestore.Update{
	{
		Path:  "capital",
		Value: firestore.Delete,
	},
})

if err != nil {
	return err
}
```

###コレクションを削除する

```go:app.go
ref *firestore.CollectionRef, batchSize int) error {

	for {
		// Get a batch of documents
		iter := ref.Limit(batchSize).Documents(ctx)
		numDeleted := 0

		// Iterate through the documents, adding
		// a delete operation for each one to a
		// WriteBatch.
		batch := client.Batch()

		for {
			doc, err := iter.Next()

			if err == iterator.Done {
				break
			}

			if err != nil {
				return err
			}

			batch.Delete(doc.Ref)
			numDeleted++
		}

		// If there are no documents to delete,
		// the process is over.

		if numDeleted == 0 {
			return nil
		}

		_, err := batch.Commit(ctx)

		if err != nil {
			return err
		}
	}
```

##あとがき
今回も前回に引き続きAppEngine Goの内容となりました。
まだまだ日本語ドキュメントが豊富ではないAppEngine Goをこれから始める人の参考になればと思います。
間違っている点などありましたらコメント等でお知らせ下さい。