Go
GORM
goa

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

はじめに

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

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)

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