[golang] FirestoreのDocumentを色々な方法でクエリする


この記事は Voicy Advent Calendar 2020 の ２５日目の記事です。
前日は 　@saicologicさんの [エンジニアの仕事と死にゲーと私](https://qiita.com/saicologic/items/3b908acc888fedfc084a) でした。

# はじめに
はじめに、やりたいこととして、__複数のドキュメントを一度にIDで取得したい__という目標があり、その目標を達成するまでに色々試したクエリを記していきます。 
前提条件として、取得したいドキュメントのIDは自動生成されており、取得したいドキュメントのIDは手元にあり、配列に入っていると仮定します。

### 自動生成されたドキュメントIDを使用する動機
[Cloud Firestore のベスト プラクティス](https://firebase.google.com/docs/firestore/best-practices?hl=ja)によると、非連続の自動生成されたIDを使うのが良いとのことです。

![https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_152050_5fed4ff1-cfd8-d2f5-111a-c84fc44dc58b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/887071/b8ba59fd-457f-a04c-e69a-1df582e2ec84.png)


# 1. ひとつのドキュメントの取得
まずは基本に立ち返って、一つのドキュメントの取得方法について考えます。
自動生成されたIDをもつドキュメントでも、一つだけであればかんたんに取得できます。
取得したいドキュメントのIDとコレクション名が分かっていれば下記のようなクエリで取得できます。


```golang

client.Collection("collection-name")
  .Doc("AuTOGEneRatEdID")
  .Get(ctx)
```

# 複数ドキュメントの取得（フィールドにもIDが登録されている場合）
単一のDocumentの取得について考えたので、次は複数のDocumentを取得する方法を考えます。

ドキュメントにフィールドがある場合はかんたんにそのフィールドでクエリすれば簡単に目的のDocumentを取得できます。[Cloud Firestore で単純なクエリと複合クエリを実行する(公式ドキュメント)。](https://firebase.google.com/docs/firestore/query-data/queries?hl=ja)
日本語版のドキュメントには記載がないが、同ページの英語版には以下のように記載されています。[Perform simple and compound queries in Cloud Firestore](https://firebase.google.com/docs/firestore/query-data/queries?hl=en#in_and_array-contains-any)

![スクリーンショット 2020-12-27 19.14.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/887071/f579e6ab-bb0a-78b9-d2af-dba3197d4152.png)

上記を参考にクエリすると、以下のようになります。

```golang
client.Collection("collection-name")
  .Where("document_id", "in", documentIds)
  .GetAll(ctx)
```

ただし、DocumentのID（パスで使われる方）とDocumentのフィールドのIDの値は被ってしまいます。



# 複数ドキュメントの取得(直接IDでクエリする場合)
自動生成されているIDを、ひとつひとつのドキュメントのフィールドに登録するのはあまりやりたくないので、他の手法を考えます。

調べてみると、```__name__``` は事前に用意されたフィールドで、オブジェクトのpathを返してくれるそうです。
なので、```__name__```に対して、自動生成されているIDを指定してあげれば、指定のIDのドキュメントが取得できます。```__name__```はgolangのfirestoreのpackageでは、```firestore.DocumentID```と定義されているのでそれを使います。

```golang
// DocumentID is the special field name representing the ID of a document
// in queries.
const DocumentID = "__name__"
```

下記のようにクエリします。オブジェクトのパスを対象にするので、自動生成されたIDでも使えるし、Where句を使っているので、不等号でなくても'in'ももちろん使えます。

```golang
clinet.Collection("collection-name")
  .Where(
    firestore.DocumentID,
    "in", 
    documentIds
  )
  .GetAll(ctx)
```
これで当初の目標通りにDocumentを取得できます。

# __ name __ がなぜかGolangでは機能しない問題
ところがそう上手くはいかず、pythonでもNodejsでも__ name __を指定すればIDでクエリできるのだが、Golangではなぜかうまく動いてくれなかった・・・（自分の環境だけかも）ので、代替案を考えます。

# 複数ドキュメントの取得(直接IDでクエリする場合)その２
手法としては、取得したいDocumentのIDを指定してDocumentRefを作成して取得するというもの。
最終的には、下記のようにクエリすることで、目的通りに複数のIDでfirestoreのDocumentを取得することができました。

```golang

// 取得したいドキュメントを持つCollectionを取得
collectionRef := client.Collection("collection-name")

// 取得したいDocumentのIDを指定して、DocumentRefを作成する
tmpDocs := make([]*firestore.DocumentRef, len(documentIDs))
for idx, id := range documentIDs {
    tmpDocs[idx] = collectionRef.Doc(strconv.FormatInt(id, 10))
}

//DocumentRefを一気に取得する
docs, err := r.firestoreClient.GetAll(ctx, tmpDocs)
if err != nil {
	return err
}

```

# おわりに
もっと良い方法が公式ドキュメントのどこかにちゃんと記載されているのかもしれない。
その場合はリンクを貼るので教えて下さい。

年末までもう少し、みなさん、良い年をお迎えください！！