mongoDB公式のGoのDriverを使ってみた

## 概要
[mongoDB公式のGo用のDriver](https://github.com/mongodb/mongo-go-driver)触ったので、そのまとめ

## 環境
go 1.12.5
go.mongodb.org/mongo-driver v1.1.2
mongoDB 3.1

## 用語
[わかりやすくまとめてくれてる方がいた](https://qiita.com/yuji0602/items/c55e2cb75376fd565b4e#rdb%E3%81%A8mongodb%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E6%A7%8B%E9%80%A0%E3%81%AE%E6%AF%94%E8%BC%83)

- table → collection
- row → document
- column → field

## 接続
公式ドキュメントの[Usage](https://github.com/mongodb/mongo-go-driver#usage)見た方が早い気がしやすが一応

```go
import (
	"context"
	"log"

	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://username:password@localhost:27017"))
	if err != nil {
		log.Fatalln(err)
		return
    }
}
```

`ApplyURI("mongodb://username:password@localhost:27017")`ここで接続先指定してる感じ

## FindOne
1ドキュメントの取得
検索条件を構造体で作成した場合

```go
import (
    "go.mongodb.org/mongo-driver/bson/primitive"
    // NOTE: 他に必要なパッケージは省略
)

type FindOneRequest struct {
    // NOTE: 検索したいfieldとvalueを適宜定義する
    TargetField string `json:"targetField" bson:"targetField"`
}

type FindOneResponse struct {
    ID              primitive.ObjectID `json:"id" bson:"_id"`
	Hoge            string             `json:"hoge" bson:"hoge"`
	Fuga            string             `json:"fuga" bson:"fuga"`
}

func main() {
    // NOTE: 接続処理は省きやす 上の見てくだされ

    collection := client.Database("db_name").Collection("collection_name")

	request := FindOneRequest{
		TargetField: "検索内容",
	}

	var response FindOneResponse
	err = collection.FindOne(context.Background(), request).Decode(&response)
	if err == mongo.ErrNoDocuments {
		log.Println("Documents not found")
	} else if err != nil {
		log.Fatalln(err)
	}

	log.Println(res)
}
```

構造体使う場合は`bson`タグ使ってドキュメントで使用している項目名を指定すればいい感じにマッピングしてくれる
ドキュメント作ると勝手に生成してくれるObjectIDは`primitive.ObjectID`型を使えばよき

## Find
複数ドキュメントの取得
今回は検索条件をbson.Dで定義し、レスポンスにはbson.Mを使用

```go
import (
    "go.mongodb.org/mongo-driver/bson"
    // NOTE: 他に必要なパッケージは省略
)

func main() {
	 // NOTE: 接続処理は省きやす

	collection := client.Database("db_name").Collection("collection_name")

    // NOTE: 検索したいfield名とvalueを定義
	cur, err := collection.Find(ctx, bson.D{{Key: "field_name", Value: "value"}})
	if err != nil {
		log.Fatal(err)
	}
	defer cur.Close(ctx)
	for cur.Next(ctx) {

        // NOTE: 1ドキュメントずつdecode
        //       mapが返ってくる
		var result bson.M
		err := cur.Decode(&result)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(result)
	}
	if err := cur.Err(); err != nil {
		log.Fatal(err)
	}
}
```

Find使った時に返り値には単純な配列とかが返ってくるわけでなく、
`*Cursor`型が返ってくるのでここらへんの扱いが独特だと感じた次第

## InsertOne
1ドキュメントの追加

```go
import (
    "go.mongodb.org/mongo-driver/bson/primitive"
    // NOTE: 他に必要なパッケージは省略
)

type InsertOneRequest struct {
    ID              primitive.ObjectID `json:"id" bson:"_id"`
	Hoge            string             `json:"hoge" bson:"hoge"`
	Fuga            string             `json:"fuga" bson:"fuga"`
}

func main() {
	// NOTE: 接続処理は省きやす

    collection := client.Database("db_name").Collection("collection_name")

	request := InsertOneRequest{
		ID: primitive.NewObjectID(),
		Hoge: "hoge",
		Fuga: "fuga",
	}

	// NOTE: InsertOneの返り値には作成したドキュメントのObjectIDが返ってくる
	response, err := collection.InsertOne(context.Background(), request)
	if err != nil {
		log.Fatalln(err)
	}
}
```

作成したドキュメント使って何かしたいときは`request.ID`使ってFindOneした構造体を使う感じになりますかね


## InsertMany
複数ドキュメントの登録

```go
import (
    "go.mongodb.org/mongo-driver/bson/primitive"
    // NOTE: 他に必要なパッケージは省略
)

type Request struct {
    ID              primitive.ObjectID `json:"id" bson:"_id"`
	Hoge            string             `json:"hoge" bson:"hoge"`
	Fuga            string             `json:"fuga" bson:"fuga"`
}

func main() {
	// NOTE: 接続処理は省きやす

    collection := client.Database("db_name").Collection("collection_name")

	var insertManyRequest []interface{}

	for index := 0; index < 10; index++ {
		indexString := strconv.Itoa(index)

		request := Request{
			ID:   primitive.NewObjectID(),
			Hoge: "hoge",
			Fuga: "fuga",
		}

		insertManyRequest = append(insertManyRequest, question)
	}

	// NOTE: 今回の場合、一括で10件登録してくれる
	//       返り値はinsertしたドキュメントのObjectIDの配列
	response, err := collection.InsertMany(context.Background(), insertManyRequest)
	if err != nil {
		log.Fatalln(err)
	}
}
```

InsertManyメソッドの第二引数は`[]interface{}`型


## Aggregate
集計は[Aggregate](https://docs.mongodb.com/manual/reference/method/db.collection.aggregate/)ってやつでできるらしい
mongoDBには[Operators](https://docs.mongodb.com/manual/reference/operator/)ってのがあってCollectionに対して、このOperatorsを使ってく感じですね

Operatorsの中でもAggregateで使うのが[Pipeline](https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/)
公式ドキュメントに比較的Exampleが書いてるのでそれとにらめっこするといつの間にか実装できる

```go
import (
	"go.mongodb.org/mongo-driver/bson"
	// NOTE: 他に必要なパッケージは省略
)

func main() {
	// NOTE: 接続処理は省きやす

	collection := client.Database("db_name").Collection("collection_name")

	pipeline := []bson.M{
		bson.M{
			"$match": bson.M{
				"targetField": "value",
			},
		},
		bson.M{
			"$group": bson.M{
				"_id": "$targetFieldID",
				"count": bson.M{
					"$sum": 1,
				},
			},
		},
	}

	// NOTE: AggregateはFindと同様に`*Cursor`型を返すので、Cursor型用の扱い方が必要
	hogeAggre, err := collection.Aggregate(ctx, pipeline)
	if err != nil {
		log.Fatalln(err)
	}

	defer hogeAggre.Close(ctx)

	for hogeAggre.Next(ctx) {
		var result bson.M
		err := answerAggre.Decode(&result)
		if err != nil {
			log.Fatal(err)
		}

		fmt.Println(result)
	}
	if err := answerAggre.Err(); err != nil {
		log.Fatal(err)
	}
}
```

出力結果は下記のような感じのを返してくれるはず
厳密には違うかもしれないので悪しからず

```json
[
	{
		"_id": "hogehoge",
		"count": 15,
	},
	{
		"_id": "fugafuga",
		"count": 13,
	},
]
```

`$`がついてるのがOperatorですね
今回Pipelineで指定したのは`collection_name`ってコレクションの`targetField`フィールドの値が`value`のドキュメントたちを`targetFieldID`フィールドでGroupByして`targetFieldID`ごとのドキュメント数を`count`フィールドに算出させた感じです
多分・・・

OperatorとかPiplineのStageとかはもっとドキュメント読み込む必要がありやす

## 所感
InsertやFindするときの引数や返り値は構造体でしっかり型定義してたけども、結局ドキュメントの構造は同一のコレクションでも異なる可能性が出てくるのでbson.Mとかみたくしっかり型定義しないほうがいい気もした