マイクロサービスの作成

マイクロサービスやってますか？
私は今イギリスの会社でソフトウェアエンジニアとして日々マイクロサービス開発をしています。
イギリスで働くことになった経緯などは[こちら](https://note.mu/kentakudo/n/n3c48effa1817)に書き残したのでもし興味があればのぞいてみてください。

弊社では３年前からKubernetesを使ったマイクロサービス開発を行なっており、本番で運用するための知見が多くあります。今回、アドベントカレンダーというイベントを通して海外でのマイクロサービス開発の一事例を紹介していけたらなと思います。

まっさらな状態から徐々にサービスを育てていく形式で進めていこうと思います。
ノリで区切っていくので25日より短いかもしれないし、25日では足りないかもしれません。笑
どうか温かく見守っていただければと思います。

アプリケーションのレポジトリは[こちら](https://github.com/KentaKudo/qiita-advent-calendar-2019)です。
ステップごとにコミットしていくのでバージョンを切り替えながら見てください。

では早速サービスを作っていきましょう。

## リポジトリ作成

クリーンなリポジトリを作成したら以下の作業をします。

- `go mod init`
- main.goファイルの作成

`go build`でバイナリが作成できるか試してみましょう。

## Hello, world
今回は [jawher/mow.cli](https://github.com/jawher/mow.cli) ライブラリを使ってアプリを書いていきます。
シンプルで使いやすいためオススメです。

`Hello, world`を出力するコードはこんな感じになります。

```go
package main

import (
	cli "github.com/jawher/mow.cli"

	"log"
	"os"
)

const (
	appName = "qiita-advent-calendar-2019"
	appDesc = "The micro service sample app"
)

func main() {
	app := cli.App(appName, appDesc)

	app.Action = func() {
		log.Println("Hello, world")
	}

	if err := app.Run(os.Args); err != nil {
		log.Fatal(err)
	}
}
```

早速ビルドして走らせてみましょう。

```sh
$ go build
$ ./qiita-advent-calendar-2019 
2019/11/14 14:44:24 Hello, world
```

## バイナリにgithashの値を埋め込む

githashをコンパイル時に埋め込み、コード内からアクセスできるようにしておきます。何か問題が発生した際などにどのバージョンのアプリなのかすぐわかるようできるなど便利なことがあります。
ついでに [sirupsen/logrus](https://github.com/sirupsen/logrus) ライブラリを使ってログをいい感じにします。

```go
package main

import (
	"os"

	cli "github.com/jawher/mow.cli"
	log "github.com/sirupsen/logrus"
)

var gitHash = "overriden at compile time"

const (
	appName = "qiita-advent-calendar-2019"
	appDesc = "The micro service sample app"
)

func main() {
	app := cli.App(appName, appDesc)

	app.Action = func() {
		log.WithField("git_hash", gitHash).Println("Hello, world")
	}

	if err := app.Run(os.Args); err != nil {
		log.WithError(err).Fatal("app run")
	}
}
```

ビルドコマンドはgithashを埋め込む記述が入るためこうなります。

```sh
$ go build -ldflags "-X main.gitHash=$(git rev-parse HEAD)"
$ ./qiita-advent-calendar-2019 
INFO[0000] Hello, world                                  git_hash=2e94c00b197a3b1e131c09ce56545e56527a1ba0
```

logrusライブラリでは出力形式をjsonにすることもできますし、ログレベルの設定も簡単です。

## サーバープロセスの開始

では、サーバープロセスを走らせてみましょう。

新しいプロセスはgoルーティンで開始し、メインのルーティンでは子ルーティンからのエラーメッセージを待ち受ける形で同期します。

- プロセスの立ち上げ

```go

errCh := make(chan error, 1)
go func() {
	http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello, world")
	})
	if err := http.ListenAndServe(net.JoinHostPort("", strconv.Itoa(*srvPort)), nil); err != nil {
		errCh <- errors.Wrap(err, "server")
	}
}()
```

- 子ルーティンの待ち受け

```go
select {
	case err := <-errCh:
		log.Println(err)
}
```

なお、サーバのポート番号は変更可能なように環境変数として受け取れるようにします。mow.cliライブラリではこんな感じに記述します。

```go
srvPort := app.Int(cli.IntOpt{
	Name:   "srv-port",
	Desc:   "http server port",
	EnvVar: "SRV_PORT",
	Value:  8080,
})
```

では、実行してcurlしてみましょう。

```sh
$ go build -ldflags "-X main.gitHash=$(git rev-parse HEAD)"
$ ./qiita-advent-calendar-2019                             
INFO[0000] Hello, world                                  git_hash=86bd5b5d84ec8b02329d80e62c5c76ecf2ded099
```

```sh
$ curl localhost:8080/bar
Hello, world
```

# さいごに

シグナルを受けとった際にも安全に終了できるように、select句で同時に待ち受けておきましょう。

```go
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

select {
	case err := <-errCh:
		log.Println(err)
	case <-sigCh:
		log.Println("termination signal received. attempt graceful shutdown")
}
```

```sh
$ ./qiita-advent-calendar-2019 
INFO[0000] Hello, world                                  git_hash=d2cf7e593de77bc49a98a76fd1ee25f49bdaf795
^CINFO[0004] termination signal received. attempt graceful shutdown 
INFO[0004] bye
```

---

明日は、アプリケーションをデプロイしていきたいと思います。