Edited at

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

More than 1 year has passed since last update.


はじめに

もうほかに選択肢無いでしょ的なライブラリを除くと主に使ってるのは以下くらいです。

思っていた以上に標準パッケージで筋肉なコード書いていたようです 😇

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のコメントで通知されるのがかなり便利です。


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

image.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貼り忘れとかにちょいちょい気づけて良いです。

(/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:

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を使わない


    • そもそも現状そんな処理ないが... 😇



// 実際にログ吐くところの書き方はこんな感じ

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)

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