Go API開発の環境構築 テンプレート アーキテクチャ、ホットリロード、ロギング、Docker
GoでAPIを作るときのテンプレです。 ホットリロード、ロギング、コンテナ、DB、まとめて構築していきます。
以下の技術を使用します。
- Docker docker-compose
- ホットリロードライブラリair https://github.com/cosmtrek/air
- フレームワークgin https://github.com/gin-gonic/gin
- ORM gorm v1 https://gorm.io/
- db mysql
ディレクトリ構成です。
├── Dockerfile ├── air.toml //airの設定 ├── config │ ├── config.go │ └── development.yml ├── db │ └── my.cnf ├── docker-compose.yml ├── entry.sh ├── go.mod ├── go.sum ├── main.go ├── src │ └── infrastructure │ ├── db │ │ └── sqlhandler.go │ ├── logging │ │ └── logging.go │ └── router │ └── router.go └── tmp └── main
クリーンアーキテクチャでの実装を前提としています。
Dockerfile
Dockerfile
FROM golang:latest as development WORKDIR /app VOLUME /app ADD . /app ENV GO_ENV=development RUN apt-get update && apt-get -y install default-mysql-client RUN go get github.com/cosmtrek/air CMD ["./entry.sh"]
docker-compose側でマルチビルドしたいので as developmentとします。 開発環境用なので環境変数に developmentを渡します。 buildに必要な mysql clientと airをインストールします。
docker-compose
version: '3.7' services: app: build: context: . dockerfile: Dockerfile target: development ports: - "8080:8080" volumes: - "./:/app" security_opt: - seccomp:unconfined depends_on: - db environment: DB_PORT: 3306 DB_NAME: sample_pj DB_USER: root DB_PASSWORD: password db: platform: linux/x86_64 #for m1Tip image: mysql:8.0.25 environment: MYSQL_DATABASE: sample_pj MYSQL_ROOT_PASSWORD: password ports: - "3306:3306" volumes: - ./data/mysql:/var/lib/mysql:cached - ./db/my.cnf:/etc/mysql/conf.d/my.cnf
m1チップのMac Bookではplatform: linux/x86_64を指定する必要があります。
entry.sh
#!/bin/sh set -e $GOPATH/bin/air -c air.toml
set -e https://qiita.com/youcune/items/fcfb4ad3d7c1edf9dc96#set--e -cオプション はコンフィグファイルの指定https://github.com/cosmtrek/air#usage
air.toml
root = "." tmp_dir = "tmp" [build] cmd = "go build -o ./tmp/main ./main.go" bin = "tmp/main"
次にGoのコードを書いていきます。
config/config.go
package config import ( "fmt" "io/ioutil" "log" "os" "sync" "github.com/caarlos0/env" "gopkg.in/yaml.v2" ) type Config struct{ Port string Db Db } type Db struct { Port string `env:"DB_PORT"` Name string `env:"DB_NAME"` User string `env:"DB_USER"` Password string `env:"DB_PASSWORD"` } var config *Config var once sync.Once func Get() *Config { return config } func init() { once.Do(func(){ config = &Config{} goenv := os.Getenv("GO_ENV") filepath := fmt.Sprintf("./config/%v.yml", goenv) buf, err := ioutil.ReadFile(filepath) if err != nil { log.Fatalln("failed to load config yaml err:", err) } fmt.Println("config:", config) err = yaml.Unmarshal(buf, config) if err != nil { log.Fatalln("failed to Unmarshal yaml err: ", err) } if err := env.Parse(&config.Db); err != nil { fmt.Printf("failed to load Db error: %s", err) } }) }
ここでは環境変数の設定を行います。 環境変数はDockerfileとdocker-composeの両方から渡しています。 os.Getenvで読み込む GO_ENVはDockerfile env アノテーションで読み込むDB_ はdocker-composeからです。
ioutil.ReadFileでconfig配下のymlファイルを読み込みます。
development.yml
port: 8080
これでGO_ENV環境変数毎にymlを切り替えます。
次にsrc配下のGoファイルを作っていきます。 今回はクリーンアーキテクチャを前提にFrameworks & Drivers に当たるinfrastructureの部分を実装していきます。
src/infrastructure/db/sqlhandler.go
package db import ( "fmt" "log" "github.com/username/sample_pj/config" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" ) type SQLHandler struct { Conn *gorm.DB } func New() *SQLHandler { connectionString := genConnectString() fmt.Println("connectionString: ", connectionString) conn, err := gorm.Open("mysql", connectionString) if err != nil { log.Fatal("db connection error: ", err) } fmt.Println("Connection OK") conn.LogMode(true) // debug mode sqlHandler := &SQLHandler{ Conn: conn, } return sqlHandler } func genConnectString() string { conf := config.Get() USER := conf.Db.User PASSWORD := conf.Db.Password PROTOCOL := fmt.Sprintf("tcp(db:%s)", conf.Db.Port) DBNAME := conf.Db.Name QUERY := "?charset=utf8mb4&parseTime=True&loc=Local" return USER + ":" + PASSWORD + "@" + PROTOCOL + "/" + DBNAME + QUERY }
*gorm.DBを返します。
src/infrastructure/logging/logging.go
package logging import ( "fmt" "io" "log" "os" ) type Logger struct { F *log.Logger I *log.Logger D *log.Logger E *log.Logger } type NullWriter struct{} func (n NullWriter) Write(p []byte) (int, error) { return 0, nil } func NewLogger(env string) *Logger { var fatalWriter io.Writer = os.Stderr var infoWriter io.Writer = os.Stdout var errorWriter io.Writer = os.Stderr var debugWriter io.Writer = os.Stdout if env != "development" { debugWriter = NullWriter{} } return &Logger{ F: log.New(fatalWriter, "[FATAL] ", log.Llongfile|log.Ldate|log.Lmicroseconds), E: log.New(errorWriter, "[ERROR] ", log.Llongfile|log.Ldate|log.Lmicroseconds), I: log.New(infoWriter, "[INFO] ", log.Llongfile|log.Ldate|log.Lmicroseconds), D: log.New(debugWriter, "[DEBUG] ", log.Llongfile|log.Ldate|log.Lmicroseconds), } } func (l *Logger) Info(v ...interface{}) { l.I.Output(2, fmt.Sprint(v...)) } func (l *Logger) Infof(format string, v ...interface{}) { l.I.Output(2, fmt.Sprintf(format, v...)) } func (l *Logger) Debug(v ...interface{}) { l.D.Output(2, fmt.Sprint(v...)) } func (l *Logger) Debugf(format string, v ...interface{}) { l.D.Output(2, fmt.Sprintf(format, v...)) } func (l *Logger) Error(v ...interface{}) { l.E.Output(2, fmt.Sprint(v...)) } func (l *Logger) Errorf(format string, v ...interface{}) { l.E.Output(2, fmt.Sprintf(format, v...)) } func (l *Logger) Fatal(v ...interface{}) { l.F.Output(2, fmt.Sprint(v...)) os.Exit(1) } func (l *Logger) Fatalf(format string, v ...interface{}) { l.F.Output(2, fmt.Sprintf(format, v...)) os.Exit(1) }
標準logライブラリのマッパーです。 外部パッケージでlogger.Debug("debug")のように使用できます。 可変引数についてhttps://golang.org/ref/spec#Passing_arguments_to_..._parameters logについて https://waman.hatenablog.com/entry/2017/09/29/011614
src/infrastructure/router/router.go
package router import ( "net/http" "github.com/username/sample_pj/config" "github.com/username/sample_pj/src/infrastructure/db" "github.com/gin-gonic/gin" ) func Dispatch(sqlhandler *db.SQLHandler) { conf := config.Get() r := gin.Default() r.GET("/hello", func(ctx *gin.Context) { ctx.JSON(http.StatusOK, gin.H{ "message": "hello", }) }) r.Run(":" + conf.Port) }
/hello にアクセスした時, {"message": "hello"} を返します。
main.go
package main import ( "github.com/username/sample_pj/src/infrastructure/db" "github.com/username/sample_pj/src/infrastructure/logging" "github.com/username/sample_pj/src/infrastructure/router" ) func main() { logger := logging.NewLogger("development") logger.Debug("debug log") sqlhandler := db.New() defer sqlhandler.Conn.Close() router.Dispatch(sqlhandler) }
docker-compose up
以上。