GoでCRUDアプリをMysql、GORM、Echo、Clean Architectureで作る

#はじめに


本記事では `Clean Architecture` で、
DBは `Mysql` 、フレームワークは `Echo` 、ORMapperには `GORM` を用いて
 `CR(U)D機能を持つAPI` 作っていきます。


### 作るもの

Create、Read、(Update)、Delete機能を持つAPI
Updateだけ未実装ですので、ぜひご自身で追加してみてください！

### 対象読者

Goの環境構築まで終えて、簡単なAPIを作ってみたい人


### 使用技術

|技術 |種類  |
|---|---|
|DB  |Mysql  |
|ORM  |GORM  |
|Framework  |Echo |


# 目次


# Clean Architectureとは

Clean Architectureというと、以下の図が大変有名です。

![clean.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633802/b785a780-20e9-96ae-52ae-597a7f4866d5.jpeg)


Clean Architectureの目的は**関心の分離**で、
これを達成するために意識すべきことが**各レイヤーの依存性**です。

関心の分離によりコードの可読性が向上したり、変化に強い設計になります。
この辺のメリットやClean Architectureの詳細に関しては[参考記事](https://qiita.com/hirotakan/items/698c1f5773a3cca6193e)に載っていますのでそちら参照ください。



上記の図では、円の外側から内側に向かって矢印が向けられていますが、これが**依存の向き**で、
外側から内側への依存は可能ですが、内側から外側は不可能です。

言い方を変えると、**内側で宣言したモノを外側から呼ぶことはできます**が、**外側で宣言したモノを内側から呼ぶことはできない**という話です。

この記事では**依存の向きに注意しながら** コードを紹介していきます。




# 各機能に関して

各機能のエンドポイントは以下の通りです

 `POST: /users `
 `GET: /users`
 `DELETE: /users/:id`



# ディレクトリ構成

```
echoSample
│
│── src
│    ├── domain 
│    │    └── user.go
│    ├── infrastructure
│    │    ├── router.go
│    │    └── sqlHandler.go
│    ├── interfaces
│    │    ├── api
│    │        ├── context.go
│    │        └── user_controller.go
│    │    └── database
│    │        ├── sql_handler.go
│    │        └── user_repository.go
│    ├── usecase
│    │    ├── user_interactor.go
│    │    └── user_repository.go
│    ├── server.go
│        
└── docker-compose.yml 
```

Clean Arhictectureの レイヤーと今回のディレクトリ構成は
以下のように照らし合わせることができます。

|ディレクトリ名  |レイヤー  |
|---|---|
|domain  |Entities  |
|usecase  |Use Cases  |
|interface  |Controllers Presenters  |
|infrastructure  |External Interfaces  |



# domain
domain層ではEntityの定義をします。
中心にあるので、どの層からでも呼び出せます。
![entitys.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633802/524daffe-4bf8-b361-b27b-10d4e5ef554a.png)



```src/domain/user.go
package domain

type User struct {
	ID   int    `json:"id" gorm:"primary_key"`
	Name string `json:"name"`
}
```

今回はカラムにIDとNameを持つ `User` を作り、idをプライマリキーに設定します。


`json:"id" gorm:"primary_key"` について


`json:"id"`ではjsonマッピングをしていて
`gorm:"primary_key"`ではgormでモデルにタグづけしています。
もちろん、`primary_key` の他にも `not null` ` unique` `default` などsqlでテーブル定義する要領と同じようにできます。

参考
[GORM Declearing Model](https://gorm.io/ja_JP/docs/models.html)



# infrastructure


最も外側にある **Infrastructure** 
アプリケーションが外部と関わる部分を書きます、今回の例では**DBとの接続**と、**Router** をここで定義しています。
![framework.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633802/77765d3e-556a-ed96-5b99-71163680f091.png)


一番外側なので、どの層も意識することなく呼び出せます。

```src/infrastucture/sqlhandler.go
package infrastructure

import (
	"gorm.io/driver/mysql"
	"gorm.io/gorm"

	"echoSample/src/interfaces/database"
)

type SqlHandler struct {
	db *gorm.DB
}

func NewSqlHandler() database.SqlHandler {
	dsn := "root:password@tcp(127.0.0.1:3306)/go_sample?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err.Error)
	}
	sqlHandler := new(SqlHandler)
	sqlHandler.db = db
	return sqlHandler
}

func (handler *SqlHandler) Create(obj interface{}) {
	handler.db.Create(obj)
}

func (handler *SqlHandler) FindAll(obj interface{}) {
	handler.db.Find(obj)
}

func (handler *SqlHandler) DeleteById(obj interface{}, id string) {
	handler.db.Delete(obj, id)
}

```

DB接続周りは `公式ドキュメント` が参考になりました。

[gorm.io](https://gorm.io/ja_JP/docs/create.html)

続いて、ルーティングです。
今回はWebフレームワークのEchoを使っています。こちらも `公式ドキュメント` を参考にしました。APIのMethodやパスの定義をしています。
[echo.labstack](https://echo.labstack.com/)



```src/infrastructure/router.go
package infrastructure

import (
	controllers "echoSample/src/interfaces/api"
	"net/http"

	"github.com/labstack/echo"
)

func Init() {
	// Echo instance
	e := echo.New()
	userController := controllers.NewUserController(NewSqlHandler())

	e.GET("/users", func(c echo.Context) error {
		users := userController.GetUser() 
		c.Bind(&users) 
		return c.JSON(http.StatusOK, users)
	})

	e.POST("/users", func(c echo.Context) error {
		userController.Create(c)
		return c.String(http.StatusOK, "created")
	})

	e.DELETE("/users/:id", func(c echo.Context) error {
		id := c.Param("id")
		userController.Delete(id)
		return c.String(http.StatusOK, "deleted")
	})

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


```



# interfaces

Controllers Presenters の層です。


ここからは、**依存性を意識する** 必要があります。 

![framework-controller.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633802/d336f861-eafa-c013-0e52-bc3a8a6049bc.png)

**interface層**から**domain層**、**usecase層**の呼び出しに関しては問題ありませんが

**infrastructure層**の呼び出しはできないので、直接呼び出すのではなく、interfaceを定義します。(ややこしいですが、inrastrucure層で定義しているsqlHandlerのinterfaceです)



```src/interfaces/api/user_controller.go
package controllers

import (
	"echoSample/src/domain"
	"echoSample/src/interfaces/database"
	"echoSample/src/usecase"

	"github.com/labstack/echo"
)

type UserController struct {
	Interactor usecase.UserInteractor
}

func NewUserController(sqlHandler database.SqlHandler) *UserController {
	return &UserController{
		Interactor: usecase.UserInteractor{
			UserRepository: &database.UserRepository{
				SqlHandler: sqlHandler,
			},
		},
	}
}

func (controller *UserController) Create(c echo.Context) {
	u := domain.User{}
	c.Bind(&u)
	controller.Interactor.Add(u)
	createdUsers := controller.Interactor.GetInfo()
	c.JSON(201, createdUsers)
	return
}

func (controller *UserController) GetUser() []domain.User {
	res := controller.Interactor.GetInfo()
	return res
}

func (controller *UserController) Delete(id string) {
	controller.Interactor.Delete(id)
}

```

controllerではusecase層、domain層から呼び出しをしているので問題ありません。




```src/interfaces/api/context.go
package controllers

type Context interface {
	Param(string) string
	Bind(interface{}) error
	Status(int)
	JSON(int, interface{})
}

```


database関連

```src/interfaces/database/user_repository.go
package database
package database

import (
	"echoSample/src/domain"
)

type UserRepository struct {
	SqlHandler
}

func (db *UserRepository) Store(u domain.User) {
	db.Create(&u)
}

func (db *UserRepository) Select() []domain.User {
	user := []domain.User{}
	db.FindAll(&user)
	return user
}
func (db *UserRepository) Delete(id string) {
	user := []domain.User{}
	db.DeleteById(&user, id)
}
```


repositoryでは、sqlHandlerを呼んでいますが、infrastructure層のモノを直接呼んでいるのではなく
同階層に定義した**sqlhandlerのinterface**を通じて呼んでいます。

これを**依存性逆転の原則**と言うそうです。


```src/interfaces/db/sql_handler.go
package database

type SqlHandler interface {
	Create(object interface{})
	FindAll(object interface{})
	DeleteById(object interface{}, id string)
}

```

これでsql_handlerの処理を呼び出せますね。



# usecase
最後になりましたが、usecase層です。

![controller-usecase.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633802/b56ffe16-f142-07a5-a9f0-53fcdfcfcc50.png)


```src/usecase/user_interactor.go
package usecase

import "echoSample/src/domain"

type UserInteractor struct {
	UserRepository UserRepository
}

func (interactor *UserInteractor) Add(u domain.User) {
	interactor.UserRepository.Store(u)
}

func (interactor *UserInteractor) GetInfo() []domain.User {
	return interactor.UserRepository.Select()
}

func (interactor *UserInteractor) Delete(id string) {
	interactor.UserRepository.Delete(id)
}

```


ここでも先ほどと同じように、依存性逆転の原則を適用する必要があります。

なのでuser_repository.goを定義します。

```src/usecase/user_repository.go
package usecase

import (
	"echoSample/src/domain"
)

type UserRepository interface {
	Store(domain.User)
	Select() []domain.User
	Delete(id string)
}

```


これで実装が一通り終了しました。




あとはdocker-compose.ymlでmysqlを立ち上げ、サーバを起動すれば動くはずです。

```docker-compose.yml
version: "3.6"
services:
  db:
    image: mysql:5.7
    container_name: go_sample
    volumes:
      # mysqlの設定
      - ./mysql/conf:/etc/mysql/conf.d
      - ./mysql/data:/var/lib/mysql
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    ports:
      - 3306:3306
    environment:
      MYSQL_DATABASE: go_sample
      MYSQL_ROOT_PASSWORD: password
      MYSQL_USER: root
      TZ: "Asia/Tokyo"

```

```src/server.go
package main

import (
	"echoSample/src/domain"
	"echoSample/src/infrastructure"

	"github.com/labstack/echo/v4"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

var (
	db  *gorm.DB
	err error
	dsn = "root:password@tcp(127.0.0.1:3306)/go_sample?charset=utf8mb4&parseTime=True&loc=Local"
)

func main() {
	dbinit()
	infrastructure.Init()
	e := echo.New()
	e.Logger.Fatal(e.Start(":1323"))
}

func dbinit() {
	db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
	}
	db.Migrator().CreateTable(domain.User{})
}


```

以下のコマンドでmysql起動

```
docker-compose up -d
```

サーバ起動

```
go run server.go
```

# 動作確認

 `POST: /users `


```
curl --location --request POST 'localhost:1323/users' \
--header 'Content-Type: application/json' \
--data-raw '{
    "name":"J.Y Park"
}'

curl --location --request POST 'localhost:1323/users' \
--header 'Content-Type: application/json' \
--data-raw '{
    "name":"Eron Mask"
}'


```
docker-compose

```
created%                                                                                                                                                                                                               
```

 `GET: /users`

```
curl --location --request GET 'localhost:1323/users'
```

```
[{"id":1,"name":"J.Y Park"},{"id":2,"name":"Eron Mask"}]
```


 `DELETE: /users/:id`

```
curl --location --request DELETE 'localhost:1323/users/1'
```

```
deleted
```

getして確かめる

```
{"id":2,"name":"Eron Mask"}]
```


# まとめ

Clean Architectureに関する記事を読むだけではなく、実際に簡単なAPIを作って動作確認まで行ってみると、理解が深まりますね。

ただ、CRUDアプリ程度の規模だと、Clean Architectureの魅力が体感できないと言うのが正直な感想でした。

Clean Architectureは、コードの可読性や生産性の向上にもつながるだけではなく、**変化に強い**という性質も持っているので、今回作ったモノにいろいろ付け加えて良さを実感したいなと思います...!

English Version
https://dev.to/michinoins/building-a-crud-app-with-mysql-gorm-echo-and-clean-architecture-in-go-h6d


### 参考記事
[世界一わかりやすいClean Architecture](https://www.slideshare.net/AtsushiNakamura4/clean-architecture-release)
[クリーンアーキテクチャ(The Clean Architecture翻訳)](https://blog.tai2.net/the_clean_architecture.html)
[Clean ArchitectureでAPI Serverを構築してみる](https://qiita.com/hirotakan/items/698c1f5773a3cca6193e)
[Golang - EchoとGORMでClean Architecture APIを構築する](https://qiita.com/so-heee/items/0cca93008eae635c642a)