概要

初めて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サーバのコード

https://github.com/zakiry/golang_api_sample

ディレクトリは以下のような考え方で分けた、だいぶ自己流

  • 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サーバのコードを書いてみると戸惑うことが多かったので、同じような境遇にある誰かの参考になればと思います。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.