Golang - EchoとGORMでClean Architecture APIを構築する

# はじめに
Goのフレームワークを最近触り出して、アーキテクチャについて学んだ方がいいなと感じていた所、下記の記事に出会いAPIサーバーを構築してみることにしました。アーキテクチャのアの字も知らなかったので大変参考になりました。

[Clean ArchitectureでAPI Serverを構築してみる](https://qiita.com/hirotakan/items/698c1f5773a3cca6193e)

せっかくなので以下の二点変更点を加えて構築していきます。

- フレームワークをGinからEchoに変更
- ORM（GORM）を導入

サンプルは[こちら](https://github.com/so-heee/golang_echo_example)のリポジトリにあげてありますので宜しければ参照ください。

# 構成

Dockerを利用して、APIとMySQLをそれぞれ用意しました。

```bash
└── golang_echo_example
    ├── api
    │   ├── domain
    │   ├── infrastructure
    │   ├── interfaces
    │   │   ├── controllers
    │   │   └── database
    │   ├── server.go
    │   └── usecase
    ├── docker
    │   ├── api
    │   │   └── Dockerfile
    │   └── mysql
    │       └── Dockerfile
    ├── docker-compose.yml
    └── mysql
        ├── conf.d
        └── initdb.d
```

# Echoを利用したルーティングの実装

## Frameworks & Drivers層
LoggerとRecoverのミドルウェアを有効にします。エンドポイントは以下のものを実装しました。

- ユーザー情報取得：GET /user/:id
- ユーザー一覧取得：GET /users
- ユーザー情報登録：POST /user/:id
- ユーザー情報更新：PUT /user/:id
- ユーザー情報削除：DELETE /user/:id

```go:infrastructure/router.go
package infrastructure

import (
	"net/http"

	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
	"github.com/so-heee/golang_echo_example/api/interfaces/controllers"
)

func Init() {
	// Echo instance
	e := echo.New()

	userController := controllers.NewUserController(NewSqlHandler())

	// Middleware
	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	e.GET("/", hello)
	e.GET("/users", func(c echo.Context) error { return userController.Index(c) })
	e.GET("/users/:id", func(c echo.Context) error { return userController.Show(c) })
	e.POST("/create", func(c echo.Context) error { return userController.Create(c) })
	e.PUT("/users/:id", func(c echo.Context) error { return userController.Save(c) })
	e.DELETE("/users/:id", func(c echo.Context) error { return userController.Delete(c) })

	// Start server
	e.Logger.Fatal(e.Start(":1323"))
}
```

## Interfaces/controllers層
各エンドポイントごとにコントローラーを実装します。
ここからはGinの場合と変わらないので省略します。

```go:interfaces/controllers/user_controller.go
func (controller *UserController) Create(c echo.Context) (err error) {
	u := domain.User{}
	c.Bind(&u)
	user, err := controller.Interactor.Add(u)
	if err != nil {
		c.JSON(500, NewError(err))
		return
	}
	c.JSON(201, user)
	return
}
```

# ORM(GORM)を利用したMySQL操作

## Frameworks & Drivers層
infrastructureレイヤーにデータベース接続を実装しています。gormをimportしてデータベースとの接続を行います。またGORMのCRUD Interface単位でメソッドを定義し、`interfaces/database層`から呼び出します。

```go:infrastructure/sqlhandler.go
package infrastructure

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

	"github.com/so-heee/golang_echo_example/api/interfaces/database"
)

type SqlHandler struct {
	Conn *gorm.DB
}

func NewSqlHandler() database.SqlHandler {
	conn, err := gorm.Open("mysql", "root:root@tcp(mysql)/sample?charset=utf8&parseTime=True&loc=Local")
	if err != nil {
		panic(err.Error)
	}
	sqlHandler := new(SqlHandler)
	sqlHandler.Conn = conn
	return sqlHandler
}

func (handler *SqlHandler) Find(out interface{}, where ...interface{}) *gorm.DB {
	return handler.Conn.Find(out, where...)
}

func (handler *SqlHandler) Exec(sql string, values ...interface{}) *gorm.DB {
	return handler.Conn.Exec(sql, values...)
}

func (handler *SqlHandler) First(out interface{}, where ...interface{}) *gorm.DB {
	return handler.Conn.First(out, where...)
}

func (handler *SqlHandler) Raw(sql string, values ...interface{}) *gorm.DB {
	return handler.Conn.Raw(sql, values...)
}

func (handler *SqlHandler) Create(value interface{}) *gorm.DB {
	return handler.Conn.Create(value)
}

func (handler *SqlHandler) Save(value interface{}) *gorm.DB {
	return handler.Conn.Save(value)
}

func (handler *SqlHandler) Delete(value interface{}) *gorm.DB {
	return handler.Conn.Delete(value)
}

func (handler *SqlHandler) Where(query interface{}, args ...interface{}) *gorm.DB {
	return handler.Conn.Where(query, args...)
}

```

## Interfaces/database層
実際にMySQLとのやり取りを実装していきます。GORMのQueryを利用することで割とすっきりと書くことができました。後の部分は参考と同じ実装で問題ないので省略します。

```go:interfaces/database/user_repository.go
func (repo *UserRepository) FindById(id int) (user domain.User, err error) {
	if err = repo.Find(&user, id).Error; err != nil {
		return
	}
	return
}
```

# 動作確認
APIとMySQLのコンテナを起動します。

```bash
// コンテナの立ち上げ
$ docker-compose up -d

// MySQLの初期化スクリプトを実行
$ ./init-mysql
```

localhost:1323/usersでユーザー一覧が無事取得できました。

```bash
$ curl -i -H 'Content-Type:application/json' localhost:1323/users                                              
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Date: Fri, 25 Oct 2019 06:19:20 GMT
Content-Length: 249

[{"ID":1,"FirstName":"Patricia","LastName":"Smith"},{"ID":2,"FirstName":"Linda","LastName":"Johnson"},{"ID":3,"FirstName":"Mary","LastName":"William"},{"ID":4,"FirstName":"Robert","LastName":"Jones"},{"ID":5,"FirstName":"James","LastName":"Brown"}]
```





# 参考
[Clean ArchitectureでAPI Serverを構築してみる](https://qiita.com/hirotakan/items/698c1f5773a3cca6193e)
[echo 公式ガイド](https://echo.labstack.com/)
[docker-compose でMySQL環境簡単構築](https://qiita.com/A-Kira/items/f401aea261693c395966)