Go初心者が、GolangとMySQLを接続してAPIを作ってみた

# 概要

- 先日 [DOG#2](https://techplay.jp/event/735792) で登壇した際の知見をQiitaにまとめました
- 実際登壇した際の[スライド](https://speakerdeck.com/k_yoshikawa/golangtomysqlwojie-sok-site-apiwozuo-tutemita)です
- ソースコードは[GitHub](https://github.com/hunhunyosshy/black-and-white)に上がっています

# 留意点

- スライドにもある通り、私がGoに触った時間は当時15時間くらいの超初心者です
- 当時とにかく動く事を第一の目的にして作ったので、まだまだ穴が多いと思っています

# 前提条件

- バージョンは以下の通りです
    - Go:go1.12.6 darwin/amd64
    - MySQL:5.7.26
- ローカル環境で実装しました

# 流れ

1. 作ろうとしているものの説明
2. GoでMySQLを接続する
3. 環境変数を設定する
4. SQL接続処理だけ共通ファイルにまとめる
4. Json形式で返すWeb APIを作成する

# 詳細

## 1. 作ろうとしているものの説明

- 2019年現在の私のマイブームが将棋です(こちらも超初心者です)
- 同僚などと指した際の勝敗結果と内容(戦法/囲い)を管理しようと考えています
- ここでは戦法(英語でopeningというらしい)の一覧取得のみを目指します
- MySQLのデータは以下のようなものを用意しました
    - `shogi` データベースの中に `opening` テーブルを作成してます

```
+------------+-----------------+
| opening_id | name            |
+------------+-----------------+
|          1 | なし            |
|          2 | 居飛車棒銀      |
|          3 | 三間飛車        |
|          4 | 四間飛車        |
|          5 | 向い飛車        |
|          6 | 中飛車          |
+------------+-----------------+
```

## 2. GoでMySQLを接続する

[GoでMySQLに接続する](https://qiita.com/taizo/items/54f5f49c6102f86194b8) や [Package sql](https://golang.org/pkg/database/sql/)を参考にさせていただきました。

### [MySQL](https://github.com/go-sql-driver/mysql)パッケージのインストール

```
$ go get -u github.com/go-sql-driver/mysql
```

### MySQLの接続確認

```mysqltest.go
package main

import (
	"database/sql"
	"fmt"

	_ "github.com/go-sql-driver/mysql"
)

func main() {
	db, err := sql.Open("mysql", "****:****@/shogi")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	id := 3
	var name string
	err = db.QueryRow("SELECT name FROM opening WHERE opening_id = ?", id).Scan(&name)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(name)
}
```

### 接続結果

```
mysqltest $ go build
mysqltest $ ./mysqltest
三間飛車
```

これで無事取得することができました

## 環境変数を設定する

DBのパスワードをgit管理内に置いておくのはよくありません。
個人開発ならまだしも、会社でこんなものを書いたら、レビューの嵐ですね。。
.envファイルで設定していきます。

### [godotenv](https://github.com/joho/godotenv)パッケージのインストール

```
go get github.com/joho/godotenv
```

### .envの設定
```
DB_NAME=shogi
DB_ROLE=****
DB_PASSWORD=****
```
### .envをLoadして反映
```mysqltest.go
// "os" "github.com/joho/godotenv" をそれぞれインポートしてください
func main() {
	err := godotenv.Load()
	if err != nil {
		log.Fatal(err)
	}
	db, err := sql.Open("mysql", os.Getenv("DB_ROLE") + ":"+os.Getenv("DB_PASSWORD") + "@/" + os.Getenv("DB_NAME"))
	// 以下割愛
```

これで、ソースコード内にパスワードを置く必要がなくなりました。


## 4. SQL接続処理だけ共通ファイルにまとめる

1ファイルだけならこのまま `main.go` でいいかもしれませんが、
接続のたびに毎回接続処理を書くのは正直よくありません。
接続処理だけ切り出し、main.goではそれを呼び出すだけにします。

### 処理のファイルの置き場所を決める

[project-layout](https://github.com/golang-standards/project-layout) を読んだ結果 、 `pkg/` 配下に置くことにしました。

### 共通処理にして切りだす

SQLを接続して、成功した場合にDB型を返すようにします

```pkg/db/connect.go
func Connect() *sql.DB {
	err := godotenv.Load()
	if err != nil {
		panic(err.Error())
	}
	db, err := sql.Open("mysql", os.Getenv("DB_ROLE") + ":"+os.Getenv("DB_PASSWORD") + "@/"+os.Getenv("DB_NAME"))
	if err != nil {
		panic(err.Error())
	}
	return db
}
```

### 共通処理を呼び出す

データの取得処理自体も `pkg/dao/opening/openingdao.go` としてで切り出します。
(これは共通処理というよりビジネスロジックから切り離そうとした結果です)
データを構造体のスライスに格納して返すようにします。

```pkg/dao/opening/openingdao.go
package openingdao

import (
	"github.com/hunhunyosshy/black-and-white/pkg/db"
)

//Opening型の構造体を用意します
//あとでjson形式にするので、jsonのタグをあらかじめつけておきます
type Opening struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func FetchIndex() []Opening {
	db := db.Connect()
	defer db.Close()

	//rowを取得
	rows, err := db.Query("SELECT * FROM opening")
	if err != nil {
		panic(err.Error())
	}
	//Opening型のスライスに格納します
	openingArgs := make([]Opening, 0)
	for rows.Next() {
		var opening Opening
		err = rows.Scan(&opening.ID, &opening.Name)
		if err != nil {
			panic(err.Error())
		}
		openingArgs = append(openingArgs, opening)
	}
	return openingArgs
}
```

## 5. Json形式で返すWeb APIを作成する

APIとして使えるよう、URIを叩けばJson形式にして返すようにします。
今回は、ルーティング処理を、gorilla/mux というライブラリでやってみました。
(ちなみに採用理由は昔ゴリというあだ名をつけられていて、親近感が湧いたためです)

### [gorilla/mux](https://github.com/gorilla/mux) パッケージのインストール

```
go get -u github.com/gorilla/mux
```

### Json形式で返すようにする

```main.go
package main

import (
	"encoding/json"
	"log"
	"net/http"

	"github.com/gorilla/mux"

	openingdao "github.com/hunhunyosshy/black-and-white/pkg/dao/opening"
)

func main() {
	r := mux.NewRouter()
	// localhost:8080/opening/ で戦法の一覧を取得できるようにします
	r.HandleFunc("/opening/", showOpeningIndex)
	log.Fatal(http.ListenAndServe(":8080", r))
}

func showOpeningIndex(w http.ResponseWriter, r *http.Request) {
	opening := openingdao.FetchIndex()
	//json形式に変換します
	bytes, err := json.Marshal(opening)
	if err != nil {
		log.Fatal(err)
	}
	w.Write([]byte(string(bytes)))
}
```

### 実際に返すか確認する

![スクリーンショット 2019-06-30 2.04.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195687/bba08416-14d4-6cf1-7a43-b8115e3d3595.png)

無事返すことができました。めでたしめでたし

# まとめ(というか感想？)

- 色々ハマったが、ひとまず簡単なWEB APIを作れるようにはなった気がする
- 環境変数の設定、jsonへの変換は比較的楽だった
- ライブラリは有効活用できるかは、公式ドキュメントをどれだけ読み込めるかによる
    - もし会社でやるなら、ライブラリの技術選定は大事になる気がする
- まだまだまとめられることはあると思うので、引き続き実装していきたい

# 参考にしたリンク

[GoでMySQLに接続する](https://qiita.com/taizo/items/54f5f49c6102f86194b8)
[Package sql](https://golang.org/pkg/database/sql/)
[インポート宣言](http://golang.jp/go_spec#Import_declarations)
[pkgの説明](https://github.com/golang-standards/project-layout/tree/master/pkg)
[Goにはディレクトリ構成のスタンダードがあるらしい。](https://qiita.com/sueken/items/87093e5941bfbc09bea8)
[Go 言語で gorilla/mux を使った簡単なウェブアプリのサンプル](http://nirasan.hatenablog.com/entry/2016/03/07/192150)
[Go言語でServer作る時に必要な知識メモ](https://qiita.com/awakia/items/ef8978fc33ffb1d27460)