Go-lang+GinでAPIサーバを作る一歩目～EHW2018「アプリ②」

# 概要

このエントリは、「Enterprise "hello, world" 2018 Advent Calendar 2018」の12/16向けのものです。このAdvent Calendarでは、複数個のエントリにまたがる話の流れも鑑みつつ、なるべく1エントリで１つのトピックをカバーできるようにする予定です。

このエントリで記載するトピックは、Go-lang+GinでAPIサーバを作る一歩目です。1つのAPIを作って、テストします。

# 前提

## おことわり

- このEnterpfise "hello, world"シリーズは、ネタのためのエントリです。実環境でそのまま利用ことを目的とはしていません。
- 動かしやすさを優先してセキュリティを意図的に低くする設定など入れてありますのでご注意ください。

## 想定読者

「Enterprise "hello, world" 2018」的なネタとしては、下記のような状況を想定しています。

> 前日のフロントがアクセスするAPIサーバを作らなくてはいけない。

# 準備(Go-lang)

## go

OSなどは、EHW2018のこれまでのエントリと同じです。go-langはまだ使っていなかったので、インストールします。

```shell
$ sudo apt-get install golang
$ go version
go version go1.10.4 linux/amd64
```

Ubuntu 18.04の標準パッケージなので、最新の1.11より少し古いですが、今回の目的ではよしとします。

## パッケージ管理ツールdep

```shell
$ go get -u github.com/golang/dep/cmd/dep
```

このプロジェクトを作るディレクトリに移動して、

```
~/go/src$ mkdir ehw2018.io
$ cd ehw2018.io/
```

```shell-session
$ dep init
Importing configuration from govendor. These are only initial constraints, and are further refined during the solve process.
  Locking in  (a5b47d3) for transitive dep gopkg.in/yaml.v2
  Locking in  (57fdcb9) for transitive dep github.com/mattn/go-isatty
  Locking in master (b4a75ba) for transitive dep golang.org/x/sys
  Locking in 1.0.0 (36b1496) for transitive dep github.com/json-iterator/go
  Locking in master (22d885f) for transitive dep github.com/gin-contrib/sse
  Locking in  (c88ee25) for transitive dep github.com/ugorji/go
  Locking in  (5a0f697) for transitive dep github.com/golang/protobuf
  Using ^1.3.0 as constraint for direct dep github.com/gin-gonic/gin
  Locking in v1.3.0 (b869fe1) for direct dep github.com/gin-gonic/gin
  Locking in v8.18.1 (5f57d22) for transitive dep gopkg.in/go-playground/validator.v8
```
# Gin

[Gin](https://github.com/gin-gonic/gin)は、Go-lang向けのWebフレームワークです。

Go-langで使えるWebフレームワークには、比較記事をぱっと見た感じこのほかにも、いっぱい機能がある系の[Revel](https://revel.github.io/)や[beego](https://github.com/astaxie/beego)、シンプル系の[echo](https://github.com/labstack/echo)、[goji](https://github.com/zenazn/goji)、[Martini](https://github.com/go-martini/martini)なんかがありそうですが、このエントリでは、以下の点によりGinを使用します。

- 標準のnet/httpだけでもいいけれども、もう少し機能が欲しいこと
- API層だけでよいので、シンプルなものでよいこと
- ~~筆者が[Bombay Sapphire](http://www.bacardijapan.jp/products/gin/bombaysapphire/)というジンをロックでいただくのが好きであること~~

## source

Ginのサイトにあるチュートリアルを眺めながら書いてみると、こんな感じになりました。

```golang
package main

import (
        "fmt"
)

import (
        "github.com/fvbock/endless"
        "github.com/gin-gonic/gin"
)

var (
        Version  string
        Revision string
)

func setupRouter() *gin.Engine {
        router := gin.Default()

        // Global middleware
        router.Use(gin.Logger())

        // Routing
        router.StaticFile("/", "./index.html")

        router.GET("/api/greeting", func(ctx *gin.Context) {
                ctx.JSON(200, gin.H{
                        "message": "hello, world",
                })
        })

        return router
}

func main() {
        fmt.Println("Greetings Server : Version:" + Version + " Revision:" + Revision)

        endless.ListenAndServe(":8080", setupRouter())
}

```

上記コードで、GET /api/greeting で、挨拶文を返すことのほか、下記に対応しています。

- ミドルウェアで、Loggerを指定しています。
- ルーティングのセットアップは一つの関数にして、テストしやすくしています。
- "/"へのアクセスで、静的ファイルのindex.htmlを返すようにしています。
- ビルドされたバージョンとリビジョン(Gitのcommit ID)を出力するため、変数を用意し、後述のMakefile内の方法で値を渡して埋め込んでいます。

## Makefile

Makefileは、下記のとおりです。ビルド時の変数の埋め込み方などは、「[Go でツール書くときの Makefile 晒す](https://qiita.com/dtan4/items/8c417b629b6b2033a541)」を参考にさせていただきました。

```
NAME     := greetings-server
VERSION  := v0.0.1
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\""



all: bin/$(NAME)

depinit:
        dep init

depensure:
        dep ensure

bin/$(NAME): $(SRCS)
        go build -a -tags greetings-server -installsuffix greetings-server $(LDFLAGS) -o bin/$(NAME)

run:
        go run greetings-server.go

release-run: bin/$(NAME)
        GIN_MODE=release ./bin/greetings-server

check:
        curl http://localhost:8080/api/greeting
        echo ""

fmt:
        go fmt greetings-server.go

test:
        go test
```

## デバッグ用の実行

make runします。

```shell-session
$ make run
go run greetings-server.go
Greetings Server : Version: Revision:
[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.

[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /                         --> ehw2018.io/vendor/github.com/gin-gonic/gin.(*RouterGroup).StaticFile.func1 (4 handlers)
[GIN-debug] HEAD   /                         --> ehw2018.io/vendor/github.com/gin-gonic/gin.(*RouterGroup).StaticFile.func1 (4 handlers)
[GIN-debug] GET    /api/greeting             --> main.setupRouter.func1 (4 handlers)
14940 :8080

```

## アクセス

curlでアクセスしてみます。

```shell-session
$ make check
curl http://localhost:8080/api/greeting
{"message":"hello, world"}echo ""
```

動いてますね。

## テスト

こんな感じでテストを書きます。

```golang
package main

import (
        "net/http"
        "net/http/httptest"
        "testing"

        "github.com/stretchr/testify/assert"
)

func TestPingRoute(t *testing.T) {
        router := setupRouter()

        w := httptest.NewRecorder()
        req, _ := http.NewRequest("GET", "/api/greeting", nil)
        router.ServeHTTP(w, req)

        json := `{"message":"hello, world"}`
        assert.Equal(t, 200, w.Code)
        assert.Equal(t, json, w.Body.String())
}
```

動かしてみましょう。

```shell-session
$ make test
go test
[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.

[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /                         --> ehw2018.io/vendor/github.com/gin-gonic/gin.(*RouterGroup).StaticFile.func1 (4 handlers)
[GIN-debug] HEAD   /                         --> ehw2018.io/vendor/github.com/gin-gonic/gin.(*RouterGroup).StaticFile.func1 (4 handlers)
[GIN-debug] GET    /api/greeting             --> ehw2018%2eio.setupRouter.func1 (4 handlers)
[GIN] 2018/12/22 - 16:03:24 | 200 |     100.536μs |                 | GET      /api/greeting
[GIN] 2018/12/22 - 16:03:24 | 200 |     226.777μs |                 | GET      /api/greeting
PASS
ok      ehw2018.io      0.022s
```

テストが成功しました。

# まとめ

このエントリでは、「Enterprise "hello, world" 2018 Advent Calendar 2018」(EHW2018)の16日目として、Go-lang+GinでAPIサーバを作る一歩目をトピックとして取り上げました。

本エントリのソースコードは、[https://github.com/hrkt/greetings-server/releases/tag/0.0.1](https://github.com/hrkt/greetings-server/releases/tag/0.0.1)にあげてあります。

EHW2018のネタとしては、このあと、15日目, 16日目の部品をk8sにデプロイする予定です。