Docker環境下でGoとRedisでAPIを実装する (Part.1)

# はじめに
本記事では、Go 言語と Redis を使用し簡単なAPIの作成手順について説明する。今回は、docker-compose を使って実装していく為、環境に依存しない開発環境で実装する。
また、効率的に開発が行える様、開発の中でいくつかの工夫を施しているので参考になれば幸いである。

追伸、今回は記述量が多くなるので以下の2部構成にした。

* Part.1 : 環境構築 ~ 動作確認
* Part.2 : API実装 ~ 実装検証

その為、本記事では環境構築から動作確認までを説明する。

初めて実装していく方の為に、実行環境と予備知識としていくつか説明する。

# 1. 実行環境
* macOS Catalina Ver.10.15.2
* Docker version 19.03.5, build 633a0ea
* docker-compose version 1.24.1, build 4667896b
* golang 1.13.6
* Redis 5.0.7

【補足①】

* [Go言語](https://golang.org/)
    * 2009年、Googleによって開発されたオープンソース(OSS)のプログラミング言語。
    * 特徴として、「高速コンパイル」、「シンプルな記述」が挙げられる。

【補足②】

* [Redis](https://redis.io/)
    * キーバリュー型(KVS)の NoSQL の一種。
    * キーと5種類の値型の対応関係を格納ができる。
    * インメモリデータベースであるため、高速なデータへのアクセスが可能である。

【補足③】

* [Docker](https://www.docker.com/)
    * コンテナ型の仮想化ツール。
    * 従来のハイパーバイザ型(Hyper-V)、ホスト型(VMWareやVirtualBoxなど)に比べ軽量で、且つ利便性が高い。
    * docker-compose を使用する事で、複数のコンテナを組み合わせて1つのアプリケーションを構成できる。

# 2. 前提条件
今回、実装するに至って用意するべき事項は以下の2点である。

* PC内に docker、docker-composeがインストールされている
* Goの開発環境が整備されている

docker、docker-compose が動作する環境があれば、特必要となるものは無い。コードを書く際は、好みのエディター (Atom、VSCodeなど)を使用すると良い。Goの開発環境に関しては、開発を進める上で`go.mod`、`go.sum`を事前に作成しておく必要がある為、インストールしている。

Goの開発環境に関しては、`Homebrew`を使用した環境構築がオススメである。以下にインストール手順を示しておく。

```terminal
$ brew install go
```

尚、今回使用するものが正常にインストールされているかどうかを確認したい場合は、以下のコマンドで確認が可能である。

```terminal
# Goの確認
$ go version
go version go1.13.6 darwin/amd64

# dockerの確認
$ docker version
Client: Docker Engine - Community
 Version:           19.03.5
 API version:       1.40
...

# docker-composeの確認
$ docker-compose version
docker-compose version 1.24.1, build 4667896b
docker-py version: 3.7.3
...
```
# 3. 環境構築
## 3.1. ファイル構成
今回のファイル構成は以下の通りである。

```terminal
sample_project
├── docker
│   ├── api
│   │   └── Dockerfile-api //API用
│   └── database
│       └── Dockerfile-redis //DB用
├── docker-compose.yml
└── src
    └── app
        ├── controller
        │   ├── sender.go
        │   └── receiver.go
        ├── infrastructure
        │   └── database.go
        ├── interface
        │   └── user.go
        └── main.go
```
今回は個々の処理を各ディレクトリに分けた。構成に関しては様々な設計思想があると思うので、それに基づいて開発を行う場合は、はじめにファイル構成などを考えてから構築して行くと良い。

## 3.2. Go Modulesの作成
Goでは`Go Modules`と呼ばれる外部パッケージの管理システムがある。今回はそれを使用する為、事前に`main.go`が配置されているディレクトリで以下のコマンドを実行し、`go.mod`、`go.sum`を作成しておく。

```terminal
# appディレクトリまで移動
~/sample_project $ cd src/app
# go.modファイルを作成する
~/sample_project/src/app $ go mod init main.go

# go build コマンド、または go mod download コマンドのどちらかを実行する
~/sample_project/src/app # go build
~/sample_project/src/app # go mod download
```

次に、Dockerfileの作成する。

## 3.3. Dockerfileの作成
### 3.3.1. Dockerfile (API)
まず、API用のDockerfileを作成していく。API側のDockerfileではGoの環境を構築していく。開発環境ではソースコードが随時更新される事を考慮し、ホットリロード環境を導入するために`fresh`と呼ばれるライブラリを使用した。

```dockerfile:Dockerfile-api
###############################
# Builder Container
###############################
FROM golang:1.13.6 AS builder

# Go Moduleが使えるようにする
ENV GO111MODULE=on

WORKDIR /go/src/app/

# GOROOTが/goになるため
COPY src/app /go/src/app

# gormとginのドライバをインストールする
RUN apt-get update \
    && apt-get upgrade -y \
    && apt-get install -y git \
    && go mod download \
    && go get github.com/pilu/fresh \

# ホットリロードを実行する
CMD ["fresh"]

###############################
# Production Container
###############################
FROM golang:1.13.6-alpine AS production
COPY --from=builder /go/src/app/ /app
```

上記の様に、開発環境と本番環境とで環境を分ける為、マルチステージビルドしても良い。マルチステージビルドを使用する事で、可読性、保守性を保つ事ができる。また、逐一`Dockerfile`を最適化するために書き直す面倒くささも解消できる。

尚、環境を分割する必要のない場合、以下の記述例でも良い。

```dockerfile:Dockerfile-api-slim
FROM golang:1.13.6-alpine

ENV GO111MODULE=on

WORKDIR /go/src/app/

COPY src/app /go/src/app

RUN apk add --no-cache --virtual alpine-sdk git && \
    go mod download && \
    go get github.com/pilu/fresh

CMD ["fresh"]
```

`alpine`を使用する事で、イメージが軽量化されるので、ビルド時間の短縮にもなる。しかし、RUNコマンドの記述が上記とは異なる為、注意してほしい。

※ **alpine** : Alpine Linuxと呼ばれる、BusyBoxとmuslをベースにしたLinuxディストリビューションを指す

今回は、簡単なAPIの実装なので`Dockerfile-api-slim`(alpineの方)を使用して開発を行う。

### 3.3.2. Dockerfile (Database)
DB用のDockerfileを作成していく。DB側のDockerfileではRedisの環境を構築していく。
こちら側では今回、特に設定をする事はないので下記のような記述で良い。

```dockerfile:Dockerfile-database
FROM redis:5.0.7-alpine
```

Dockerfileの作成は以上である。

## 3.4. docker-compose.yml
次に、docker-compose.ymlの作成を行う。

```yml:docker-compose.yml
version: '3.7'
services:
  api:
    build:
      context: .
      # マルチステージビルドを使用しない場合、targetは不要
      target: builder
      dockerfile: ./docker/api/Dockerfile-api
    container_name: sample_project-api
    image: sample_project/api:0.1.0
    ports:
      - "8080:8080"
    # ホストOSとコンテナ内でソースコードを共有
    volumes:
      - ./src/app:/go/src/app
    depends_on:
      - redis
    tty: true

  redis:
    build: 
      context: .
      dockerfile: ./docker/database/Dockerfile-database
    container_name: sample_project-redis
    image: sample_project/redis:0.1.0
    ports:
      - "6379:6379"
    restart: always
    volumes:
      - ./docker/database:/data
```

今回の実装の場合、DB側のコンテナの起動が完了してからAPIのコンテナを起動させたいので、
`depends_on`でコンテナの作成順序を依存関係を決めている。

また、`tty:true`を追記する事でコンテナを常時起動させる。ポート番号に関しては、デフォルトの`8080`、`6379`をそれぞれ設定している。

マルチステージビルドを採用している場合は、`target`の箇所で、自分がビルドしたいステージを指定すると良い。

## 3.5. main.go
コンテナのイメージを作成後に正しく環境構築されているかどうかを確認するために、`main.go`の作成をしておく。ここでは、簡単に作成し、後ほどAPIの開発のために改修していく。

以下に作成したソースコードを示す。

```golang:main.go
package main

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

func main() {
	router := gin.Default()
	router.GET("/", func(context *gin.Context) {
		context.JSON(200, gin.H{
			"message": "Hello, World!!",
		})
	})
	router.Run(":8080")
}
```

今回は、GoのWebフレームワークの1種である、[Gin](https://github.com/gin-gonic/gin) を使用した。処理の内容としては、`http://localhost:8080/`へアクセスすることで`Hello, World!!`とメッセージが返ってくるだけの簡単なものである。

## 3.6. dockerイメージの作成
`Dockerfile`, `docker-compose.yml`を作成後、以下のコマンドを実行し dockerイメージを作成する。

```terminal
~/sample_project $ docker-compose up -d
```

実行後、イメージができているかどうかを以下のコマンドを実行し、確認する。

```terminal
$ docker images
REPOSITORY             TAG                 IMAGE ID            CREATED             SIZE
sample_project/redis   0.1.0               b68707e68547        9 days ago          29.8MB
redis                  5.0.7-alpine        b68707e68547        9 days ago          29.8MB
sample_project/api     0.1.0               f8d6549bee6c        24 minutes ago      449MB
golang                 1.13.6-alpine       9954d1348cd8        10 days ago         359MB

```

## 3.7. コンテナの起動 / 動作確認
イメージの作成後、以下のコマンドを実行しコンテナを起動させる。

```terminal
$ docker-compose up

# docker-composeコンテナ一覧の確認
$ dokcer-compose ps
       Name                      Command               State           Ports         
--------------------------------------------------------------------------------------
sample_project-api     fresh                            Up      0.0.0.0:8080->8080/tcp
sample_project-redis   docker-entrypoint.sh redis ...   Up      0.0.0.0:6379->6379/tcp
```

コンテナを起動後、http://localhost:8080/ へアクセスするか、または`curl`コマンドを実行する事で確認できる。

```terminal
$ curl localhost:8080
{"message":"Hello, World!!"}
```

また、Redisの接続確認は以下のコマンドの通りである。

```terminal
# コンテナ内部に入る
$ docker exec -it [CONTAINER ID] sh
# redis-cliの起動
/data # redis-cli
# 以下のIPアドレスに入れればOK
127.0.0.1:6379>
```

以上まで確認できれば、一通りの環境構築・動作検証は終了である。

# 4. まとめ
今回は環境構築から動作確認までを行った。今回の構成を真似しなくとも、ホットリロード環境は便利なので今後参考にして頂ければと思う。

次回は、この続きでAPIの実装を行っていく。

続き → [GoとRedisでAPIを実装する (Part.2)](https://qiita.com/Morero/items/a8795a40c6f1bf91f292)