Go(Echo)+MySQLでCRUDを行えるAPIサーバをDocker上で構築する（1）GoとMySQLを接続し、テーブルを作成する

# はじめに
Go（Echo）を用いたAPIサーバを構築したい方向けとなっております。
この記事では、Docker上でGoとMySQlを接続できる所まで説明しています。
間違いや改善点などあればお気軽に仰っていただけると幸いです。

環境
- Docker：v24.0.6
- Docker Compose：v2.4.1

# 最終的な構成図
```
.
├── docker-compose.yml
├── golang
│   ├── Dockerfile
│   └── app
│       ├── go.mod
│       ├── go.sum
│       └── main.go
└── mysql
    ├── Dockerfile
    └── .env
```

# Docker ComposeでGoとMySQLを接続する
まず初めに、プロジェクト直下に`docker-compose.yml`を作成します。
``` docker-compose.yml
version: "3.8"
services:
    go:
        container_name: go
        build: ./golang
        tty: true
        ports:
            - "8080:8080"
        env_file:
            - ./mysql/.env
        depends_on:
            - db
        volumes:
            - ./golang/app:/go/src/app
        networks:
            - golang_test_network

    db:
        container_name: db
        build: ./mysql
        tty: true
        platform: linux/x86_64
        ports:
            - 3306:3306
        env_file:
            - ./mysql/.env
        volumes:
            - mysql_test_volume:/var/lib/mysql
        networks:
            - golang_test_network

volumes:
    mysql_test_volume:
        name: mysql_test_volume

networks:
    golang_test_network:
        external: true
```
一つづつ説明していきます。
- `version`
    - Docker Composeのバージョンです。特にこだわりがなければ最新のものを記述した方が良いと思います。
- `services`
    - 作成したいサービス（コンテナ）を列挙します。今回は、`go`と`db`の２つのサービスを作成します。
- `container_name`
    - コンテナの名前です。（無くてもよい）
- `build`
    - 用意したDockerfileよりビルドする場合、Dockerfileへの相対パスを記述します。
    - ここで定義された情報をもとにDockerをビルドし、そのビルドしたイメージからコンテナを起動します。
- `tty`
    - ttyは標準入出力先のデバイスのことです。`tty:true`とすることでコンテナが正常終了するのを防げるため、コンテナの中に入ってサーバを立てたりすることができます。
- `ports`
    - ポートの開放を行います。`ホストのポート：コンテナのポート`のように記述します。
    - ホストでポートが被らないように、`8081:8080`とするのも良いと思います。
- `env_file`
    - ファイルに定義された環境変数を読み取り、コンテナへ定義します。
    - ymlに直接記述しても出来ますが、個人的には分けた方が可読性も保守性も上がるのでお勧めです。
- `depends_on`
    - 依存関係を定義してコンテナの起動順序を制御します。
    - 上記の例では、dbコンテナが起動した後にgoコンテナが起動されます。
- `volumes`
    - コンテナの外部に保管し、データを永続化します。
    - `ホスト側の相対パス：コンテナの絶対パス`のように記述します。
- `networks`
    - Dockerコンテナ間で通信を行うためのもので、同名のネットワークが記述されたコンテナ間で通信を行うようになります。
- `platform`
    - どのプラットフォームに対応したイメージを生成するのか指定します。

### Goの設定
次に、Goのディレクトリを作成し、以下の構成にします。
```
.
├── docker-compose.yml
└── golang
    ├── Dockerfile
    └── app
        └── main.go
```

### golang/Dockerfile
``` Dockerfile
FROM golang:1.19.2-alpine
ENV GO111MODULE on

RUN apk update && \
    apk --no-cache add git && \
    apk add bash

RUN mkdir /go/src/app
WORKDIR /go/src/app

CMD ["go", "run", "main.go"]
```
- 1行目：Dockerイメージのベースとなるイメージを指定します。
- 2行目：go moduleをONにします。これにより、go.modでパッケージの管理を行うようにします。
- 4~6行目：コンテナ内にgitやbashをインストールします。
- 8~9行目：ディレクトリを作成し、作業スペースとして指定します。
- 11行目：オプションを指定し、コマンドを実行します。

golang直下にappディレクトリを作成し、main.goを作成します。
データベース接続には、GoのORMであるgormを使用しています。
### golang/app/main.go
``` main.go
package main

import (
	"fmt"
	"log"
	"os"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

// MySQLに作成するUserテーブルの定義
type User struct {
	// gorm.Modelをつけると、idとCreatedAtとUpdatedAtとDeletedAtが作られる
	gorm.Model

	Name string
	Age  int
}

// DBを起動させる
func dbInit() *gorm.DB {
	// [ユーザ名]:[パスワード]@tcp([ホスト名]:[ポート番号])/[データベース名]?charset=[文字コード]
	dsn := fmt.Sprintf(`%s:%s@tcp(db:3306)/%s?charset=utf8mb4&parseTime=True`, 
            os.Getenv("MYSQL_USER"), os.Getenv("MYSQL_PASSWORD"), os.Getenv("MYSQL_DATABASE"))
	// DBへの接続を行う
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})

	// エラーが発生した場合、エラー内容を表示
	if err != nil {
		log.Fatal(err)
	}
	// 接続に成功した場合、「db connected!!」と表示する
	fmt.Println("db connected!!")
	return db
}

func main() {
	// DB起動
	db := dbInit()
	// Userテーブル作成
	db.AutoMigrate(&User{})
}

```
`os.Getenv(...)`は、後に記述するmysql/.envで記述する環境変数を使用しています。
`dbInit()`関数でMySQLのDBに接続を行い、`db.AutoMigrate(&User{})`でUserテーブルをMySQL上に作成します。

### go.modとgo.sumの作成
`cd golang/app`でappディレクトリに移動し、`go mod init app`と`go mod tidy`コマンドを実行して2つのファイルを作成します。

### MySQL側の設定
次に、MySQL側のディレクトリを作成し、Dockerfileと.envを記述します。
構成はこのようになります。
```
.
├── docker-compose.yml
├── golang
│   ├── Dockerfile
│   └── app
│       ├── go.mod
│       ├── go.sum
│       └── main.go
└── mysql
    ├── Dockerfile
    └── .env
```
### mysql/Dockerfile
``` Dockerfile
FROM mysql:8.0
ENV LANG ja_JP.UTF-8
```
### mysql/.env
```
MYSQL_DATABASE=test_database
MYSQL_USER=test_user
MYSQL_PASSWORD=password
MYSQL_ROOT_PASSWORD=root_password
```

### Docker Composeで接続確認
`docker compose up`コマンドを実行し、「db connected!!」の記述があるか確認します。
``` bash
go-practice-go-1  | go: downloading github.com/jinzhu/now v1.1.5
go-practice-go-1  | go: downloading github.com/jinzhu/inflection v1.0.0
go-practice-go-1  | db connected!!
```
※`network golang_test_network declared as external, but could not be found`と出た場合は、`docker network create golang_test_network`を実行してネットワークの生成を行います。

### 実際にテーブルが作成されているか確認
次に、新しいターミナルを起動し、`docker compose exec db bash`を実行してdbコンテナ内に入ります。
`bash-〇.〇#`と出てくれば成功です。
次に、`mysql -u root -p`を実行します。
`Enter password:`と出てくるので、mysql/.envに記述した`MYSQL_ROOT_PASSWORD`を打ってEnterを押します。
※今回のものをそのまま使用する場合は、`root_password`でOKです。

以下のように出ればMySQLに接続できます。
```
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 8.0.35 MySQL Community Server - GPL

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>
```
ここで、`use test_database;`を実行し使用するデータベースを決めます。
最後に、`show tables;`を実行してusersテーブルが作成されているか確認します。
```
mysql> show tables;
+-------------------------+
| Tables_in_test_database |
+-------------------------+
| users                   |
+-------------------------+
1 row in set (0.06 sec)
```
ありました( ´∀｀ )
これでGoとMySQLの接続を行い、Goからテーブルを作成することができました。

# さいごに
ここまで読んでくださり、ありがとうございます。
次回は、Go（Echo）でAPIサーバを構築し、確認する所まで行いたいと思います。
次回：[Go(Echo)+MySQLでCRUDを行えるAPIサーバをDocker上で構築する（2）APIサーバを構築する](https://qiita.com/Senritsu420/items/0d9452c519bf0ec9d742)