概要
初めてGo言語でAPIサーバを開発するときに試行錯誤して学んだことをまとめました。
書いてあること
- 開発するときに便利だったツール
- パッケージの構成
- UnitTest
書いてないこと
- Goのインストール(GOPATH, GOROOTとか)
- Goの文法
- 各ツールの細かい設定や使い方
Go言語で開発するときに便利なツール
ghq & peco
git操作とリポジトリの移動を簡単にしてくれる便利なツール。
使い方とかはすでに記事がたくさんあるので省略。
go get ...
と同じディレクトリ構造でクローンできるようになり、リポジトリ間の移動が便利になった。
dep
https://github.com/golang/dep
依存関係をいい感じにやってくれる最高のツール。
dep ensure
ってやるだけで使ってるパッケージや使わなくなったパッケージをいい感じに整理してくれる。
pomファイルとかもう書きたくない。
gorm
https://github.com/jinzhu/gorm
go言語用のORM、ドキュメントが充実しててとても使いやすい。
configor
https://github.com/jinzhu/configor
コンフィグファイルを読み込むときに便利なツール。
基本的にはyamlやJSONなどから読み込むけれど、環境によって読み込むファイルを変えたり、環境変数がセットされている場合はそちらを優先する、といったことができて便利。
gomock
https://github.com/golang/mock
ユニットテスト用のモックコードを生成してくれるツール。
テストが捗る。
Testify
https://github.com/stretchr/testify
Go には assert がデフォルトでは用意されていないのでこちらを使用。
用意されてない理由はこちら
assertを使うとエラーレポートをサボるから、らしい。
とは言え、規模が小さく、ささっと動かしたかったので使用。
goreturns
https://github.com/sqs/goreturns
goのフォーマッター、インポートを勝手に整理してくれるので
セーブしたときに勝手にフォーマットされるようにすると良い。
APIサーバのコード
ディレクトリは以下のような考え方で分けた、だいぶ自己流
- cmd
- main package
- conf
- configファイルを読み込んだりするやつ
- entity
- 構造体の定義
- proto
- protoファイルとprotocで自動生成されたコード置き場
- database
- DBアクセス関連、DBへの接続や、クエリなど
- service
- ビジネスロジックが記述される部分
- mock_database
- database packageのmock, serviceのテストで使用する
HTTPサーバ
標準パッケージのnet/http
を利用すると簡単に扱うことができる。設定がシンプルでささっと開発できるし、複雑なことをやろうとしなければこれだけで十分だと思う。
twirp
twirpというRPC Frameworkを利用した。
https://github.com/twitchtv/twirp
RPCとRESTみたいな話はそれだけで記事が書ける程度に長いので省略。gRPCというのもあるけれど、今回はこっちを利用してみた。
デフォルトでJSONをサポートしてて、外部にAPIを提供する場合などまだまだJSONが必要な場面も多いので便利。
configの読み込み
configorの出番
type DbConf struct {
Dialect string
User string `env:"MysqlUser"`
Password string `env:"MysqlPassword"`
Address string `env:"MysqlAddress"`
Port int `env:"MysqlPort"`
Dbname string `env:"MysqlDbname"`
}
dialect: mysql
host: localhost
port: 3306
user: root
password:
dbname: golang_api_sample
こんな感じにすると、デフォルトではyamlを読み、環境変数がセットされていればそれで上書きしてくれる。
Unit Test
interface を使うと単体テストがやりやすくなる。
service から database を呼び出すので、テスト時には database の mock を利用している。
service が利用している database は全て interface にしているため、テスト用に適当な値を返す実装で作り変えたものを渡せばテストが簡単になる。
type mock struct{}
func (m *mock) GetById(id int64) entity.User {
return testData
}
みたいに、DBにアクセスせずに用意したテストデータを返すもので代用する。
上記は単体テストの考え方で、実際には mockgen を使って、そのメソッドが呼ばれたかどうかなどもチェックもできる。
DBのstub
sql-mockを使用している。
実行されるクエリのチェックと、そのクエリが実行されたときの結果を作れる。gormと一緒に使える。
こんな感じに書くと
mock.ExpectQuery(`SELECT id, name, age FROM "users" WHERE \(id = \?\)`).
WithArgs(userId).
WillReturnRows(records)
ExpectQueryでクエリを正規表現で比較できる。
WithArgsはWhere句の"?"に指定する値などを比較できる。
WillReturnRowsではこれまでの条件を満たす場合にrecordsを返すように設定できる。
複雑なクエリでWHERE句の指定をミスしやすいとかじゃなければここまでする必要はないかもと思う。
Makefile
GoではbuildツールにMakefileが使われることが多い。たぶんWindows以外なら簡単に使えるから。
少し調べると簡単にかけるのでざっくりとした説明だけ。
NAME := sample_app
VERSION := $(shell git describe --tags)
REVISION := $(shell git rev-parse --short HEAD)
SRCS := $(shell find . -type f -name '*.go')
LDFLAGS := -ldflags="-s -w -X \"main.Version=$(VERSION)\" -X \"main.Revision=$(REVISION)\" -extldflags \"-static\""
この辺は変数に値をセットしているだけ。
最初の方は変数に値をセットしているだけ。NAME
はアプリ名、VERSION
はGitの最新のタグ、REVISION
は最新のコミットハッシュ、SRCS
はgoのソースファイル、LDFLAGS
はビルド時に使用するオプション。
-X \"main.Version=$(VERSION)\"
はmain packageの Version 変数に Makefile の最初に定義したVERSIONをビルド時に埋め込むためのもの。
-extldflags \"-static\"
はビルド時にライブラリを静的にリンクさせるためのもの、つけないとバイナリだけコピーしてもライブラリがなくて動かない、みたいなことが起こることがある。
bin/$(NAME): $(SRCS)
は Makefileがあるディレクトリで make と打つと走る。netgoのあたりはstatic link させるためにつける。
終わりに
Goの記法はわかるけれど、実際にAPIサーバのコードを書いてみると戸惑うことが多かったので、同じような境遇にある誰かの参考になればと思います。