28
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

概要

  • 先日 DOG#2 で登壇した際の知見をQiitaにまとめました
  • 実際登壇した際のスライドです
  • ソースコードはGitHubに上がっています

留意点

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

前提条件

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

流れ

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

詳細

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

  • 2019年現在の私のマイブームが将棋です(こちらも超初心者です)
  • 同僚などと指した際の勝敗結果と内容(戦法/囲い)を管理しようと考えています
  • ここでは戦法(英語でopeningというらしい)の一覧取得のみを目指します
  • MySQLのデータは以下のようなものを用意しました
    • shogi データベースの中に opening テーブルを作成してます
+------------+-----------------+
| opening_id | name            |
+------------+-----------------+
|          1 | なし            |
|          2 | 居飛車棒銀      |
|          3 | 三間飛車        |
|          4 | 四間飛車        |
|          5 | 向い飛車        |
|          6 | 中飛車          |
+------------+-----------------+

2. GoでMySQLを接続する

GoでMySQLに接続するPackage sqlを参考にさせていただきました。

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パッケージのインストール

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 を読んだ結果 、 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 パッケージのインストール

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

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

まとめ(というか感想?)

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

参考にしたリンク

GoでMySQLに接続する
Package sql
インポート宣言
pkgの説明
Goにはディレクトリ構成のスタンダードがあるらしい。
Go 言語で gorilla/mux を使った簡単なウェブアプリのサンプル
Go言語でServer作る時に必要な知識メモ

28
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?