[Go言語] Airを使って最短でホットリロードを実現する

## 環境

以下の実行環境で動作を確認しています。

```bash
$ go version
go version go1.15.4 darwin/amd64
$ docker -v
Docker version 20.10.10, build b485636
$ docker-compose -v
docker-compose version 1.29.2, build 5becea4c
```

## ホットリロードのライブラリといえば色々ありますが・・・

最近では[Air](https://github.com/cosmtrek/air)が1番流行っているようです。サクッと試してみます。

![スクリーンショット 2021-12-05 23.31.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217116/470f3cd1-2226-a53b-b4d9-aa064b530fde.png)
[Star History](https://star-history.com/#cosmtrek/air&oxequa/realize)

## 必要なファイルを作成する

以下のファイルが必要になります。

```bash
$ tree
.
├── Dockerfile
├── air.toml
├── docker-compose.yml
├── go.mod
└── server.go
```

### シンプルなAPIサーバーを作成する

リクエストを行うと「Hello, World」とレスポンスをするシンプルなAPIサーバーを作成します。

```go:server.go
package main

import (
	"fmt"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, World")
}

func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}
```

### Dockerfileを作成する

Dockerfileを作成します。[Golang - Official Image | Docker Hub](https://hub.docker.com/_/golang) に記述されているものに少し手を加えています。

```docker:Dockerfile
FROM golang:1.17

WORKDIR /go/src/app
COPY . .

RUN go get -d -v ./...
RUN go install -v ./...

RUN go get -u github.com/cosmtrek/air

CMD ["air"]
```

### docker-compose.yml

docker-compose.ymlを作成します。

```yaml:docker-compose.yml
version: "3"
services:
  go:
    build: .
    volumes:
      - ./:/go/src/app
    ports:
      - "8080:8080"
```

### air.tomlを作成する

[Airのリポジトリ](https://github.com/cosmtrek/air/blob/master/air_example.toml) に公開されている設定ファイルを使わせてもらいます。

```toml:air.toml
# Config file for [Air](https://github.com/cosmtrek/air) in TOML format

# Working directory
# . or absolute path, please note that the directories following must be under root.
root = "."
tmp_dir = "tmp"

[build]
# Just plain old shell command. You could use `make` as well.
cmd = "go build -o ./tmp/main ."
# Binary file yields from `cmd`.
bin = "tmp/main"
# Customize binary, can setup environment variables when run your app.
full_bin = "APP_ENV=dev APP_USER=air ./tmp/main"
# Watch these filename extensions.
include_ext = ["go", "tpl", "tmpl", "html"]
# Ignore these filename extensions or directories.
exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"]
# Watch these directories if you specified.
include_dir = []
# Exclude files.
exclude_file = []
# Exclude specific regular expressions.
exclude_regex = ["_test.go"]
# Exclude unchanged files.
exclude_unchanged = true
# Follow symlink for directories
follow_symlink = true
# This log file places in your tmp_dir.
log = "air.log"
# It's not necessary to trigger build each time file changes if it's too frequent.
delay = 1000 # ms
# Stop running old binary when build errors occur.
stop_on_error = true
# Send Interrupt signal before killing process (windows does not support this feature)
send_interrupt = false
# Delay after sending Interrupt signal
kill_delay = 500 # ms

[log]
# Show log time
time = false

[color]
# Customize each part's color. If no color found, use the raw app log.
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"

[misc]
# Delete tmp directory on exit
clean_on_exit = true
```

### go.modを作成する

ターミナルで以下のコマンドを実行します。

```shell
$ go mod init air-sample
```

## 実行する

以下のコマンドを実行します。 `running...`と表示され動作していることを確認しました。

```shell
$ docker-compose up --build
...
go_1  | 
go_1  |   __    _   ___  
go_1  |  / /\  | | | |_) 
go_1  | /_/--\ |_| |_| \_ , built with Go 
go_1  | 
go_1  | watching .
go_1  | !exclude tmp
go_1  | building...
go_1  | running...
```

別のターミナルをからAPIリクエストを実行してみて、レスポンスが返ってくることを確認します。

```shell
$ curl http://localhost:8080
Hello, World
```

次に、server.goの内容を変更します。
出力を「Hello, Gopher」に変更し、保存します。

```go:server.go
func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, Gopher")
}
```

ファイルの変更を検知してビルド走ります。

```bash
go_1  | server.go has changed
go_1  | building...
go_1  | running...
```

もう一度、APIリクエストを行うとレスポンスが変化していることがわかります。

```bash
$ curl http://localhost:8080
Hello, Gopher
```

以上です。

## 参考
[cosmtrek/air: ☁️ Live reload for Go apps](https://github.com/cosmtrek/air)
[【Golang】 Airを用いてWebアプリケーション開発中に変更を即座に反映する - zuckey blog](https://blog.zuckey17.org/entry/golang-gin-air)
[Golang - Official Image | Docker Hub](https://hub.docker.com/_/golang)