Web APIつくってみる
Web APIを自分で最初から作ったことがなかったので, Goの練習も兼ねてやってみた.
やったことのまとめ
以下のようにランダムなサイコロの出目(number
)とサイコロの面の数(faces
)をJSON
で返すだけのWeb APIをGo
で作った.
# 普通に叩くと6面サイコロを振る
$ curl -X GET "localhost:8080"
{"number":1,"faces":6}
$ curl -X GET "localhost:8080"
{"number":5,"faces":6}
# クエリパラメータで面の数を指定できる
$ curl -X GET "localhost:8080?faces=100"
{"number":71,"faces":100}
# ズルをするためのエンドポイントも用意
$ curl -X GET "localhost:8080/cheat"
{"number":6,"faces":6}
$ curl -X GET "localhost:8080/cheat?faces=100&number=99"
{"number":99,"faces":100}
つかうもの
- macOS Mojave 10.14
- anyenv 1.1.1
- インストール済み
- goenv 2.0.0beta11
- インストール済み
- go version go1.14.6 darwin/amd64
- 今回入れる
やったこと
準備
まずは開発環境の準備.
公式によると現在(2020/07/16)Go
の最新版が 1.14.6 なので, これを使うようにする.
# goenvの更新
$ anyenv install goenv
anyenv: /Users/uzimihsr/.anyenv/envs/goenv already exists
Reinstallation keeps versions directories
continue with installation? (y/N) y
...
Install goenv succeeded!
Please reload your profile (exec $SHELL -l) or open a new session.
$ exec $SHELL -l
# Go 1.14.6のインストール
$ cd
$ goenv install 1.14.6
$ goenv global 1.14.6
$ goenv rehash
$ exec $SHELL -l
# 確認
$ goenv version
1.14.6 (set by /Users/uzimihsr/.anyenv/envs/goenv/version)
$ go version
go version go1.14.6 darwin/amd64
$ echo $GOPATH
/Users/uzimihsr/go/1.14.6
次にGitHubリポジトリを新規作成する.
作ったリポジトリ : https://github.com/uzimihsr/dice-api
作成したリポジトリをローカルにcloneして, Go Modules
とか.gitignore
の準備をする.Go Modules
の使い方は公式を参考にする..gitignore
はgitignore.ioを使って作るのが楽.
# 適当なディレクトリで作業
$ cd workspace
$ git clone https://github.com/uzimihsr/dice-api.git
Cloning into 'dice-api'...
warning: You appear to have cloned an empty repository.
$ cd dice-api
# Go Moduleの初期化
$ go mod init github.com/uzimihsr/dice-api
go: creating new go.mod: module github.com/uzimihsr/dice-api
$ ls
go.mod
# gitignore.ioのAPIを利用して.gitignoreを作成する
$ curl -o ./.gitignore https://www.toptal.com/developers/gitignore/api/go,macos,linux
# いったんpushしておく
$ echo "# dice-api" > README.md
$ git add .
$ git commit -m "first commit"
$ git push origin master
このときのリポジトリはこんなかんじ.
サイコロの作成
まずはサイコロの動作を作ってみる.
以前作ったものを流用する.
$ mkdir dice && cd dice
$ vim dice.go
今回は普通にサイコロの出目を返すメソッドRoll()
の他に指定した出目を返すCheat()
を作ってみた.
ためしに動かしてみる.
$ cd ..
$ vim main.go
$ go run main.go
6
5
$ go run main.go
1
5
いい感じ.
HTTPハンドラの作成
次にこれをWeb APIとして動かすためのハンドラ関数を作る.
$ mkdir handler && cd handler
$ vim handler.go
ポイントはハンドラ関数DiceHandler()
, CheatDiceHandler()
の引数でinterface
を受けるようにしているところ.
こうしておくと後でテストを書くときにmockを使いやすくなる.
これらのハンドラ関数を扱ってHTTPサーバーを動かすため, main.go
を修正する.
$ cd ..
$ vim main.go
$ go run main.go
# 動作を確認したら Ctrl+C で終了
こちらはhttp.NewServeMux()
を使ってリクエストパスごとに異なるハンドラを呼び出すようにしているのがポイント.
main.go
を実行するとMacの警告が出るので, 許可
を選択するとアプリが動く.
試しに別のターミナルからAPIを叩いてみる.
# 正常系
$ curl "localhost:8080"
{"number":1,"faces":6}
$ curl "localhost:8080?faces=12"
{"number":10,"faces":12}
$ curl "localhost:8080/cheat"
{"number":6,"faces":6}
$ curl "localhost:8080/cheat?number=1"
{"number":1,"faces":6}
$ curl "localhost:8080/cheat?number=1&faces=18"
{"number":1,"faces":18}
# 異常系
$ curl -i -X POST "localhost:8080"
HTTP/1.1 404 Not Found
Content-Type: application/json
Date: Thu, 23 Jul 2020 08:12:39 GMT
Content-Length: 24
{"message": "not found"}
$ curl -i -X GET "localhost:8080?faces=hoge"
HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Thu, 23 Jul 2020 08:13:04 GMT
Content-Length: 45
strconv.Atoi: parsing "hoge": invalid syntax
$ curl -i -X POST "localhost:8080/cheat"
HTTP/1.1 404 Not Found
Content-Type: application/json
Date: Thu, 23 Jul 2020 08:13:58 GMT
Content-Length: 24
{"message": "not found"}
$ curl -i -X GET "localhost:8080/cheat?number=hoge"
HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Thu, 23 Jul 2020 08:14:12 GMT
Content-Length: 45
strconv.Atoi: parsing "hoge": invalid syntax
サイコロの出目(number
)と面の数(faces
)がJSON
で返ってきて,
異常なクエリパラメータやHTTPメソッドでリクエストした場合には指定したステータスコード(404, 500)が返ってくることが確認できた.
やったぜ.
このときのリポジトリはこんなかんじ.
テストの作成
作りたいものは作れたのでここで終わってもいいけど, せっかくなのでテストコードを書いてみる.
まずはdice
パッケージから.
$ cd ./dice
$ vim dice_test.go
カバレッジ100%になるように書いたけど, 無駄なテストも含まれている.
次にhandler
パッケージのテストを作る.handler
パッケージは自作のdice
パッケージに依存しているので, dice
パッケージのmockを用意したい.
(今回はDBとかに接続してないのでdice
を直接呼び出してもいいんだけど, サイコロの出目が確率で変わっちゃうのでテストがしづらい)
こんなときにはgomockを使う.
mockしたいinterface
のファイルを指定するだけでmock用のコードを作成してくれるのでめちゃ便利.
# mockパッケージのインストール
$ cd ..
$ go get github.com/golang/mock/mockgen
# diceのmockを作成
$ mockgen -source ./dice/dice.go -destination ./mock_dice/mock_dice.go
このmockを使ったテストを書いてみる.
$ cd handler
$ vim handler_test.go
準備したmockを各ハンドラ関数の引数に渡してやることで, 実際のdice
を呼び出すことなくハンドラ関数のテストができるようになる.
ハンドラ関数を作るときにdice
のinterface
を引数で受けるようにしたのはこのため.
テストコードが作れたので, 実際にテストを実行する.
$ cd ..
# diceパッケージのテスト
$ go test -cover ./dice
ok github.com/uzimihsr/dice-api/dice 0.006s coverage: 100.0% of statements
# handlerパッケージのテスト
$ go test -cover ./handler
ok github.com/uzimihsr/dice-api/handler 0.016s coverage: 100.0% of statements
# 全部まとめてテスト
$ go test -cover ./...
? github.com/uzimihsr/dice-api [no test files]
ok github.com/uzimihsr/dice-api/dice (cached) coverage: 100.0% of statements
ok github.com/uzimihsr/dice-api/handler (cached) coverage: 100.0% of statements
? github.com/uzimihsr/dice-api/mock_dice [no test files]
これでテストも(一応)できた.
最終的なディレクトリの構成はこんなかんじ.
テストを実行したのでパッケージの依存関係を管理するためのファイルgo.mod
とgo.sum
が修正/追加されている.
こいつらも一緒にGit
で管理しておくと, 別の環境でこれをビルドするときに便利だったりする. らしい.
$ tree .
.
├── README.md
├── dice
│ ├── dice.go
│ └── dice_test.go
├── go.mod
├── go.sum
├── handler
│ ├── handler.go
│ └── handler_test.go
├── main.go
└── mock_dice
└── mock_dice.go
3 directories, 9 files
最終的なリポジトリはこんなかんじ.
おわり
Web APIっぽいものを0から作ってみた. テストまでやったのでけっこうしんどかった.
あとはビルドすれば普通に動くはずなので,
暇があったらKubernetes
で動かすところまでやってみたい.