クラスター社のGo製WebAPI開発で主に使ってるライブラリについて

# はじめに
もうほかに選択肢無いでしょ的なライブラリを除くと主に使ってるのは以下くらいです。
思っていた以上に標準パッケージで筋肉なコード書いていたようです 😇

- [github.com/goadesign/goa](https://github.com/goadesign/goa): WAF
- [github.com/jinzhu/gorm](https://github.com/jinzhu/gorm): DBのmappingとquerybuilder
- [go.uber.org/zap](https://github.com/uber-go/zap): logger

aws-sdk-goとか紹介することもあまり無いので割愛してます。

# github.com/goadesign/goa

- 独自のDSLでAPIの設計をすることで `go generate` とかでgoのコードとswaggerを生成している
- 設計の共通化やクライアントエンジニアと対話するときのドキュメントがコードとセットで吐き出せるので重宝している

http://kyokomi.hatenablog.com/entry/2017/06/18/000229

↑にも書きましたが、レビューしてほしいときにswaggerのURLがgithubのコメントで通知されるのがかなり便利です。

```shell:generateはこんな感じ
//go:generate goagen -o gen main --force -d github.com/kyokomi/example_goa_api/\_design
//go:generate goagen -o gen app -d github.com/kyokomi/example_goa_api/\_design
//go:generate goagen -o gen swagger -d github.com/kyokomi/example_goa_api/\_design
```

githubにこんな感じでコメントされてswagger-uiのページに遷移します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/40887/ebc3d635-f893-6918-f02e-ebdd40906efa.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/40887/fbe047db-a0e4-05e5-1d86-2030c922b5a1.png)


# github.com/jinzhu/gorm
ずっと素のSQLでいいのでは派だったのですがやっぱりDBのRowsをstructにmappingするところのコード毎回書いていくのは中々しんどかったのでgorm使ってます。
あとは、QueryBuilderは場合によってはほとんど素のSQL書いてます。

http://kyokomi.hatenablog.com/entry/2017/05/05/183332

前にこそっとブログ書いてたんですが、gormのCallbackが便利でローカルで動作させるときにexplain結果をログに出すようにしてindex貼り忘れとかにちょいちょい気づけて良いです。

```sql
(/Users/kyokomi/workspace/go/src/github.com/kyokomi/gorm-explain/example/main.go:53)
[2017-05-05 18:16:38]  [1.08ms]  INSERT INTO `products` (`created_at`,`updated_at`,`deleted_at`,`code`,`price`) VALUES ('2017-05-05 18:16:38','2017-05-05 18:16:38',NULL,'L1212','1000')

(/Users/kyokomi/workspace/go/src/github.com/kyokomi/gorm-explain/example/main.go:57)
[2017-05-05 18:16:38]  [0.95ms]  SELECT * FROM `products`  WHERE `products`.`deleted_at` IS NULL AND ((`products`.`id` = '1')) ORDER BY `products`.`id` ASC LIMIT 1
+-------+----------------+----------+---------------+---------+------------------+--------+------------+--------+---------+-------------+--------------------------------------------------------+
|    id |    select_type |    table |    partitions |    type |    possible_keys |    key |    key_len |    ref |    rows |    filtered |                                                  Extra |
+=======+================+==========+===============+=========+==================+========+============+========+=========+=============+========================================================+
|     1 |         SIMPLE |          |               |         |                  |        |            |        |         |             |    Impossible WHERE noticed after reading const tables |
+-------+----------------+----------+---------------+---------+------------------+--------+------------+--------+---------+-------------+--------------------------------------------------------+



  1 Fix example to add query trace

(/Users/kyokomi/workspace/go/src/github.com/kyokomi/gorm-explain/example/main.go:58)
[2017-05-05 18:16:38]  [1.11ms]  SELECT * FROM `products`  WHERE `products`.`deleted_at` IS NULL AND ((code = 'L1212')) ORDER BY `products`.`id` ASC LIMIT 1
+-------+----------------+-------------+---------------+---------+----------------------------+----------------------------+------------+----------+---------+-------------+---------------------------------------+
|    id |    select_type |       table |    partitions |    type |              possible_keys |                        key |    key_len |      ref |    rows |    filtered |                                 Extra |
+=======+================+=============+===============+=========+============================+============================+============+==========+=========+=============+=======================================+
|     1 |         SIMPLE |    products |               |     ref |    idx_products_deleted_at |    idx_products_deleted_at |          5 |    const |       2 |         100 |    Using index condition; Using where |
+-------+----------------+-------------+---------------+---------+----------------------------+----------------------------+------------+----------+---------+-------------+---------------------------------------+


(/Users/kyokomi/workspace/go/src/github.com/kyokomi/gorm-explain/example/main.go:61)
[2017-05-05 18:16:38]  [0.86ms]  UPDATE `products` SET `price` = '2000', `updated_at` = '2017-05-05 18:16:38'  WHERE `products`.`deleted_at` IS NULL AND `products`.`id` = '7'

(/Users/kyokomi/workspace/go/src/github.com/kyokomi/gorm-explain/example/main.go:64)
[2017-05-05 18:16:38]  [0.88ms]  UPDATE `products` SET `deleted_at`='2017-05-05 18:16:38'  WHERE `products`.`deleted_at` IS NULL AND `products`.`id` = '7'
```

サブクエリとかで色々大変な時はこんな感じ。まあ素のSQLと大差ないかなと思ってます :innocent: 

```go
func (s *HogeDBService) SelectHogeListenerByHogeID(hogeID string) ([]*HogeUserJoinTable, error) {
	var entryLogTables []*HogeUserJoinTable
	err := s.dbService.Table("hoge_entry_log").
		Select(`
			re.user_id as user_id
		`).
		Joins(`
			JOIN (
				SELECT
					MAX(hoge_entry_log.id) as id,
					hoge_entry_log.user_id,
					hoge_entry_log.hoge_id
				FROM
					hoge_entry_log
				JOIN
					hoge ON hoge_entry_log.hoge_id = hoge.id
				WHERE
					hoge_entry_log.user_id != hoge.owner_user_id
				  AND
					hoge_entry_log.hoge_id = ?
				GROUP BY
					user_id, hoge_id
			) as re
				ON re.id = hoge_entry_log.id
					AND hoge_entry_log.access_type = ?
			JOIN hoge
				ON hoge.id = re.hoge_id
					AND re.user_id != hoge.owner_user_id
		`, hogeID, HogeAccessEnter).
		Find(&entryLogTables).Error
	if err == nil && len(entryLogTables) == 0 {
		return nil, ErrRecordNotFound
	}
	return entryLogTables, err
}
```

## go.uber.org/zap
実は最近まで標準ログとSentryに通知するくらいで結構ログは適当だったんですが、どっかにログ流しこんで分析とか検索するときにjsonとかのほうがいいよねという話で色々探したところzapを選びました。

- そもそもerror系以外のログはそんなに書かない（Debugログはどうせ邪魔になる派）
- 基本的にsugarで記述する（コード書きやすさを重視）
- 1回のrequestで100万回forループするとかそういう箇所でlogを出したい場合はsugarを使わない
  - そもそも現状そんな処理ないが... 😇

```go
// 実際にログ吐くところの書き方はこんな感じ
sugar.Errorw("failed to fetch URL",
  // Structured context as loosely typed key-value pairs.
  "url", "http://localhost",
  "attempt", 3,
  "backoff", time.Second,
)
sugar.Errorw("Failed to fetch URL: %s", url)
```

次回はパッケージ構成とかの話をしようかなと思ってます。