Go RestAPI  Docker

# 今回できたこと
Goを使用してREST APIを作成した。
その際，Dockerを使用し，フレームワークには軽量なechoを使用した。
また，SSL化も行い，Let`s Encriptを使用した。

<details><summary>最初に知っておきたいコマンド</summary><div>

```
(停止)
$ docker stop <コンテナ名>

(削除)
$ docker rm <コンテナ名>

(全削除)
(コンテナ)$ docker rm -f `docker ps -a -q`
(イメージ)$ docker rmi $(docker images -q) -f

(build)
$ docker-compose up -d --build

(コンテナ起動)
$ docker-compose exec <コンテナ名> bash

(動いているdocker一覧表示)
$ docker-compose ps

(作成した全てのdocker一覧表示)
$ docker-compose ps -a 

(コンテナ内に入る)
$ docker-compose exec <コンテナ名> bash
$ docker-compose exec mysql bash

(MySQL接続)
root@コンテナ番号:/# mysql -u root -p -h 127.0.0.1

mysql> show databases;
mysql> use golang;
mysql> show tables;
mysql> SELECT * FROM `users`;

mysql> show index from users;

mysql> CREATE INDEX association_idx ON users(A, B);
```
---
</div></details>



# 全容

## 初期設定

docker, docker-compose インストール済み
[Docker のインストール手順](https://qiita.com/bezeklik/items/a6a7335acaec12edda45)
[Docker のインストール手順](https://zenn.dev/ymasaoka/articles/install-docker-centos-7)
[Docker Compose のインストール](https://docs.docker.jp/compose/install.html)

フォルダは以下を作る

```
└─プロジェクト名
  ├─server
  │ ├─docker
  │ │ ├─golang
  │ │ │ └─Dockerfile
  │ │ ├─mysql
  │ │ │ ├─Dockerfile 
  │ │ │ └─db
  │ │ │   └─init.sql
  │ │ ├─src
  │ │ │ ├─crt    <-Dockerで自動生成（証明書が入る予定）
  │ │ │ ├─databases
  │ │ │ │ └─main.go
  │ │ │ ├─routing
  │ │ │ │ └─main.go
  │ │ │ ├─go.mod <-自動生成
  │ │ │ ├─go.sum <-自動生成
  │ │ │ └─main.go
  │ │ ├─docker-compose.yml
  │ │ └─api_v1.yaml <-必要に応じて
  │ └─Server.md     <-必要に応じて
  └─README.md       <-必要に応じて
```



## 証明書作成

無料の認証局を利用する
[Let`s Encrypt](https://letsencrypt.org/ja/)
[certbot](https://certbot.eff.org/)

証明書が生成されているか確認する

```
$ ls /etc/letsencrypt/live/<自分のドメイン名>
>> README  cert.pem  chain.pem  fullchain.pem  privkey.pem
```



## プロジェクトを書き込み

### DockerComposeの設定

```docker-compose.yml
version: "3"
services:
  golang:
    build: ./golang/
    image: image_golang
    container_name: container_api
    volumes:
      - ./src:/go/src
      # 証明書を/go/src/crtへ
      - /etc/letsencrypt:/go/src/crt
    ports: 
      - "443:443"
    tty: true

  mysql:
    # platform: linux/x86_64 #AppleSiliconを使用時，必要
    build: ./mysql/
    image: image_mysql
    container_name: container_db
    volumes:
      - ./mysql/db:/docker-entrypoint-initdb.d  #初期データをマウントする場所
    environment: # コンテナ内のMySQLを起動する際のパスワードを設定
      - MYSQL_DATABASE=golang      # 作成するデータベース名
      - MYSQL_USER=akidon          # 作業ユーザー名（任意の値）
      - MYSQL_PASSWORD=12345       # 作業ユーザーのパスワード（任意の値）
      - MYSQL_ROOT_PASSWORD=root   # rootユーザーのパスワード(デフォルトでは「root」)
    ports:
      - "3306:3306"
    tty: true
```
<details><summary>説明</summary><div>
versionは3系が最新版で、versionによって書き方が異なる

build:          Dockerfileが存在する場所
image:          イメージの名前
container_name: コンテナの名前
volumes:        「./a:/b」 aはホスト側のフォルダを　bのdockerコンテナ側へ
ports:          ポート
environment:    環境変数
tty: true       コンテナ起動永続化
links:          他のコンテナの接続？

AppleSiliconを使用している場合は17行目のリマークを外す

---
</div></details>



### DockerFileの設定

```golang/Dockerfile.
# golang/Dockerfile

FROM golang:1.14

WORKDIR /go/src
```

```mysql/Dockerfile.
# mysql/Dockerfile

FROM mysql
```



### MySQLの初期設定

```init.sql
-- MySQL dump 10.13  Distrib 5.1.51, for pc-linux-gnu (i686)
--
-- Host: 127.0.0.1    Database: world
-- ------------------------------------------------------
-- Server version       5.1.51-debug-log

/*以下Databaseの内容が記載される*/

DROP SCHEMA IF EXISTS golang;
CREATE SCHEMA golang;
USE golang;

DROP TABLE IF EXISTS golang;

CREATE TABLE users
(
  id INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
  name                VARCHAR(40),
  age                 INT(10),
  created_at          TIMESTAMP DEFAULT NULL,
  updated_at          TIMESTAMP DEFAULT NULL,
  deleted_at          TIMESTAMP DEFAULT NULL
);

INSERT INTO users (id, name, age) VALUES (1, "testKun", 21);
```
<details><summary>説明</summary><div>
created_at
updated_at
deleted_at
はGormを使用する際，追加，更新，削除で必要になる要素

---
</div></details>



# Goの設定

```databases/main.go
package databases

import (
	"fmt"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)

// SQLConnect DB接続
func GormConnect() (database *gorm.DB) {
	DBMS := "mysql"                   // MySQL
	PROTOCOL := "tcp(container_db)"   // MySQLコンテナ名
	DBNAME := "golang"                // テーブル名
	USER := "akidon"                  // MySQLユーザー名
	PASS := "12345"                   // パスワード

	CONNECT := USER + ":" + PASS + "@" +PROTOCOL + "/" + DBNAME + "?charset=utf8&parseTime=True&loc=Local"  // 修正!!
	db, err := gorm.Open(DBMS, CONNECT)
	if err != nil {
		panic(err.Error())
	} else {
		fmt.Println("DB接続成功")
	}
	return db
}
```
<details><summary>説明</summary><div>
PROTOCOL := "tcp(container_db)"   // MySQLコンテナ名
のcontainer_dbはDockerComposeのcontainer_name:によるもの
間違っていれば接続できない

---
</div></details>

```routing/main.go
package routing

import (
	"fmt"
	"github.com/labstack/echo"

	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"

	"src/databases"
)

type User struct {
	gorm.Model
	Name     string `json:"name"`
	Age      int `json:"age"`
}

func (u User) String() string {
	return fmt.Sprintf("Name:%s \n Age:%d \n ",
		u.Name,
		u.Age)
}

// ユーザー情報取得
func BaseAPI_GET() echo.HandlerFunc {
	return func(c echo.Context) error {
		db := databases.GormConnect()
		defer db.Close()

		text1 := search(db)
		return c.JSON(200, text1)
	}
}

// ユーザーを登録，更新
func BaseAPI_POST() echo.HandlerFunc {
	return func(c echo.Context) error {
		db := databases.GormConnect()
		defer db.Close()

		//追加・更新
		user := new(User)
		if err := c.Bind(user); err != nil {
			return err
		}

		user1 := User{
			Name:     user.Name,
			Age:      user.Age,
		}

		insertUsers := []User{user1}
		insert(insertUsers, db)

		// update(user1, db)

		return c.JSON(200, "追加完了")
	}
}

func insert(users []User, db *gorm.DB) {
	for _, user := range users {
		db.NewRecord(user)
		db.Create(&user)
	}
}

func update(users User, db *gorm.DB) {
	var user User
	db.Model(&user).Where("id = ?", 1).Update(map[string]interface{}{"name": users.Name, "age": users.Age})
}

func search(db *gorm.DB) []User {
	var user []User
	// 同じ位置にいるリストを取得
	db.Raw("SELECT * FROM users").Scan(&user)
	return user
}
```
<details><summary>説明</summary><div>

``` routing/main.go
type User struct {
	gorm.Model
	Name     string `json:"name"`
	Age      int `json:"age"`
}
```

User(単数形)はMySQLのテーブル名「Users(複数形)」単数名にする必要がある
Name,Ageは先頭大文字である必要がある。
json:"name"はリクエストボディに関連させる名前。


---
</div></details>


```main.go
package main

import (
    "github.com/labstack/echo"
    "src/routing"
)

func main() {
    e := echo.New()
    // routing
    e.GET("/user",routing.BaseAPI_GET())
    e.POST("/user",routing.BaseAPI_POST())

    e.Logger.Fatal(e.StartTLS(":443", "/crt/live/<あなたのドメイン名>/fullchain.pem", "/crt/live/<あなたのドメイン名>/privkey.pem"))
}
```
<details><summary>HTTPでよいなら</summary><div>

```docker-compose.yml
version: "3"
services:
  golang:
    build: ./golang/
    image: image_golang
    container_name: container_api
    volumes:
      - ./src:/go/src
      # 証明書を/go/src/crtへ
      # - /etc/letsencrypt:/go/src/crt  <- 削除
    ports:
      # - "443:443"  <- 削除
      - "8080:8080"  <- 追加
    tty: true

  mysql:
    # platform: linux/x86_64 #AppleSiliconを使用時，必要
    build: ./mysql/
    image: image_mysql
    container_name: container_db
    volumes:
      - ./mysql/db:/docker-entrypoint-initdb.d  #初期データをマウントする場所
    environment: # コンテナ内のMySQLを起動する際のパスワードを設定
      - MYSQL_DATABASE=golang      # 作成するデータベース名
      - MYSQL_USER=akidon          # 作業ユーザー名（任意の値）
      - MYSQL_PASSWORD=12345       # 作業ユーザーのパスワード（任意の値）
      - MYSQL_ROOT_PASSWORD=root   # rootユーザーのパスワード(デフォルトでは「root」)
    ports:
      - "3306:3306"
    tty: true
```

```main.go
// e.Logger.Fatal(e.StartTLS(":443", "/crt/live/<あなたのドメイン名>/fullchain.pem", "/crt/live/<あなたのドメイン名>/privkey.pem"))
e.Logger.Fatal(e.Start(":8080")) // <- 追加
```

---
</div></details>

<details><summary>オレオレ証明書では</summary><div>
crtに
・ myself.csr
・ myself.key
・ myself.crt
がある場合

```main.go
// e.Logger.Fatal(e.StartTLS(":443", "./crt/live/<あなたのドメイン名>/fullchain.pem", "./crt/live/<あなたのドメイン名>/privkey.pem"))
e.Logger.Fatal(e.StartTLS(":443", "crt/myself.crt", "crt/myself.key")) // <- 追加
```

---
</div></details>



## 動かす

```
$ cd ../プロジェクト名/server/docker

(再ビルドしても変更されない場合はキャッシュが残っている可能性がある為，全削除)
(注意) $ docker rm -f `docker ps -a -q`

(ビルドする)
$ docker compose up -d --build  

(コンテナ内に入る)**1分ぐらい待つ（MySQLの初期設定の完了に時間がかかる）**
$ docker-compose exec mysql bash
root@コンテナ番号:# exit

$ docker-compose exec golang bash

(go mod init フォルダ名)
root@コンテナ番号:/go/src# go mod init src
root@コンテナ番号:/go/src# go run main.go
```






## MySQLの中を覗く

```
% docker-compose exec mysql bash

root@コンテナ番号:/# mysql -u root -p -h 127.0.0.1

(パスワードの入力を求められるので「root」を入力)
-> root

(現在存在するアカウントの確認)
(hostに「%」がついてるか確認)
mysql> select host,user from mysql.user;
+-----------+------------------+
| host      | user             |
+-----------+------------------+
| %         | akidon           |
| %         | root             |
| localhost | mysql.infoschema |
| localhost | mysql.session    |
| localhost | mysql.sys        |
| localhost | root             |
+-----------+------------------+

(ついていない場合は以下コードで追加できたはず)
mysql> grant all privileges on golang.* to 'akidon'@'%';


mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| golang             |  <- 作成できてるか確認
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.06 sec)


mysql> use golang;
mysql> show tables;
+------------------+
| Tables_in_golang |
+------------------+
| users            |   <- 作成できてるか確認
+------------------+
1 row in set (0.01 sec)


mysql> SELECT * FROM `users`;
+----+---------+------+------------+------------+------------+
| id | name    | age  | created_at | updated_at | deleted_at |
+----+---------+------+------------+------------+------------+
|  1 | testKun |   21 | NULL       | NULL       | NULL       |
+----+---------+------+------------+------------+------------+
1 row in set (0.00 sec)

```



## この時点でエラーが起きた場合

・src/main.go　27行目のcontainer_dbはdocker-composeで指定したコンテナ名を指定しているか。
・ポート解放してるか
・データベース（テーブル）が正しく作成されているか（今回ならgolang）
・大文字小文字の区別を無視していないか
・go mod init src　したか？（この辺は理解不足で間違っている場合がある。src以下のファイルをサブパッケージとして参照できるようにする？？）

・docker起動できているか以下コマンドで確認

```
# 動いているdocker一覧表示
docker-compose ps
# 作成した全てのdocker一覧表示
docker-compose ps -a 
```



## 豆知識
main.goの内容を変更してもDocker再buildする必要はない



<details><summary>SequelAce</summary><div>
![スクリーンショット 2021-04-13 15.46.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/383410d9-8b34-a9ad-4baa-0e38fab43d97.png)

---
</div></details>



<details><summary>GoENV</summary><div>

```
GO111MODULE=""
GOARCH="arm64"
GOBIN=""
GOCACHE="/root/.cache/go-build"
GOENV="/root/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="arm64"
GOHOSTOS="linux"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/root/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_arm64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/go/src/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build147035606=/tmp/go-build -gno-record-gcc-switches"
```

---
</div></details>



# 参考記事
[docker-compose upって何？](https://qiita.com/kobori_akira/items/1cd058f180206fd8d139)
[Go言語の依存モジュール管理ツール Modules の使い方](https://qiita.com/uchiko/items/64fb3020dd64cf211d4e)
他多数