GAE/Go1.11試行（その３：「GoでMVC+S構成のWebAPIを作る」）

# お題
[前回](https://qiita.com/sky0621/items/0e0a119f9fefc40e2953)はGAE/Go(v1.11)の"google.golang.org/appengine"パッケージを使って情報取得というのをやった。

今回、当初はクリーンアーキテクチャ導入にトライしてみようかと考えたものの、ディレクトリ構成を用意していくだけで大分しんどいことに気づいた。
他の記事を読んでいるだけでは特に何も感じなかったけど、実際に自分で試そうとした時、scaffoldが無いとあまりにキツい。
そして、慣れるまではどこに何があるのかわからず、コードを追うのも辛かった。

というわけで、まずは慣れ親しんだMVC+Sくらいの構成から作ってみようと思った。

# GAE試行Index
- [GAE/Go1.11試行（その2：「"google.golang.org/appengine"パッケージのappengineから取れる情報」）](https://qiita.com/sky0621/items/0e0a119f9fefc40e2953)
- [GAE/Go1.11試行（その1：「クイックスタート」）](https://qiita.com/sky0621/items/8a42ee24cb417940228c)
- [GAE/Go1.9試行（その0：「クイックスタート」）](https://qiita.com/sky0621/items/6d5541e5fa7d2ac349e7)

# 開発環境
### # OS
```
$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="17.10 (Artful Aardvark)"
```

### # Golang
```
$ go version
go version go1.11.2 linux/amd64
```

バージョンの切り替えは[goenv](https://github.com/syndbg/goenv)で行っている。

# 前提
- MVCの何たるかはなんとなくでも知っている。

# 実践

今回のソース全量は↓
https://github.com/sky0621/go-webapi-for-gae-study/tree/191f864b949b2f4801d2659220ed2ae0f7dbd631/backend

## プロジェクト構成

```
$ tree
.
├── app.yaml
├── controller
│   ├── apierror.go
│   ├── form
│   │   ├── form.go
│   │   ├── id.go
│   │   └── user.go
│   └── user.go
├── docker-compose.yml
├── go.mod
├── go.sum
├── index.yaml
├── main.go
├── model
│   ├── dto.go
│   └── user.go
├── service
│   └── user.go
└── view
```

## 説明

今回想定したのは、「ユーザ」情報を登録・取得・更新・削除するWebAPI。永続化はMySQL。
各エンドポイントは以下。

| 機能 | HTTPメソッド | パス |
| :-- | :-- | :-- |
| 登録 | POST | /api/v1/users |
| 取得 | GET | /api/v1/users/【ユーザID】 |
| 更新 | UPDATE | /api/v1/users/【ユーザID】 |
| 削除 | DELETE | /api/v1/users/【ユーザID】 |

### ■ main.go

```go
 1 func main()  {
 2 	db, err := gorm.Open("mysql", "testuser:testpass@tcp(127.0.0.1:3306)/testdb?charset=utf8&parseTime=True&loc=Local")
 3 	if err != nil {
 4 		panic(err)
 5 	}
 6 	defer db.Close()
 7 
 8 	db.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8").AutoMigrate(&model.User{})
 9 
10 	e := echo.New()
11 	defer e.Close()
12 
13 	http.Handle("/", e)
14 	g := e.Group("/api/v1")
15 
16 	controller.NewUser(db).Handle(g)
17 
18 	appengine.Main()
19 }
```

まず、２行目でMySQLに接続。
この接続情報は、あくまでローカル（docker-composeで立ち上げるMySQLへの接続限定）用。
GAE環境にデプロイする時は、接続情報はもとより、形式も下記のように変える必要がある。
"【ユーザ】:【パスワード】@unix(/cloudsql/【インスタンス接続文字列（※おそらく「【プロジェクト名】:【リージョン】:【インスタンスID】」）】)/【DB名】"

ちなみに、 `docker-compose.yml` の中身は下記。

```yaml
version: '3'
services:
  db:
    image: mysql:5.7.24
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_USER: testuser
      MYSQL_PASSWORD: testpass
      MYSQL_DATABASE: testdb
```

８行目でGoの構造体をベースにMySQL上にテーブルを自動生成。

１６行目で今回WebAPIとして提供する「ユーザ」に関するコントローラー（MVC+Sで言うCの部分）をHTTPリクエストの処理ハンドラーとして登録。

１８行目でWebサーバ起動。 `appengine.Main()` を使っているけど、GAEでのDatastoreを使おうとしなければ、おそらく直接 `http.ListenAndServe(〜〜)` を呼んでも大丈夫。

### ■ controller/user.go

`main.go` の１６行目で「 `controller.NewUser(db).Handle(g)` 」という形で呼び出されている。

【`User`の生成部分】

```go:[controller/user.go]
func NewUser(db *gorm.DB) User {
	return &user{db: db}
}

type user struct {
	db *gorm.DB
}

type User interface {
	Handle(g *echo.Group)
}
```

【Handle】

```go:[controller/user.go]
func (u *user) Handle(g *echo.Group) {
	g.POST("/users", u.createUser)
	g.GET("/users/:id", u.getUser)
	g.PUT("/users/:id", u.updateUser)
	g.DELETE("/users/:id", u.deleteUser)
}
```

それぞれのパスに対して、対応する処理メソッドを登録している。
以下、登録のメソッドをピックアップ。

```go:[controller/user.go]
 1 func (u *user) createUser(c echo.Context) error {
 2 	uf := &form.User{}
 3 	if err := c.Bind(uf); err != nil {
 4 		return c.JSON(http.StatusBadRequest, errorJSON(http.StatusBadRequest, err.Error()))
 5 	}
 6 
 7 	id, err := service.NewUser(u.db).CreateUser(uf.ParseToDto())
 8 	if err != nil {
 9 		return c.JSON(http.StatusBadRequest, errorJSON(http.StatusBadRequest, err.Error()))
10 	}
11 
12 	return c.JSON(http.StatusCreated, &form.ID{ID: id})
13 }
```

３行目でリクエストパラメータを構造体 `form.User` にパースしている。

```go:[controller/form/user.go]
type User struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}
```

７行目で「 `service.NewUser(u.db).CreateUser(uf.ParseToDto())` 」としてサービス（MVC+Sで言うSの部分）を使ってリクエストを処理。
「 `uf.ParseToDto()` 」の実装は下記の通り。

```go:[controller/form/user.go]
func (n *User) ParseToDto() *model.User {
	// フォーマット変換等は、ここで吸収
	return &model.User{
		ID:   n.ID,
		Name: n.Name,
	}
}
```

リクエストパラメータをパースしたままの構造でDB登録まで受け渡してもいいのだけど、例えば日付のような項目をJSONで文字列の「2018/12/08」として受け取って、DB登録時はTimestampに変換して登録したいとかの都合に耐える設計にするために用意。
また、このように form と model(のDTO) とを分けることで、JSONパラメータとのマッピング用のタグとDBの登録時のカラムとのマッピング（や主キー、Not Null等の制約付け）用のタグとが混在して煩雑になるのを防げる。

### ■ service/user.go

ここは、イメージとしてはデザインパターンのFacadeに当たる。複数のモデル（＝ビジネスロジック）を扱い、１つのユースケースを実現する。
トランザクション境界としての役割も担う。
とはいえ、今回のように「ユーザ」の情報を扱うだけなら、むしろ不要な”層”と言える。

```go:[service/user.go]
func (n *userService) CreateUser(m *model.User) (string, error) {
	return model.NewUserDao(n.db).CreateUser(m)
}
```

### ■ model/user.go

リクエストパラメータをパースした構造（＝`form/user.go`）から変換されたのが↓

```go:[model/user.go]
type User struct {
	ID string `gorm:"column:id;primary_key"`
	Name string `gorm:"column:name;type:varchar(255);not null"`
}
```

いわゆる DataAccessObject として振る舞う構造体の生成に関する実装は下記。

```go:[model/user.go]
func NewUserDao(db *gorm.DB) UserDao {
	return &userDao{db: db}
}

type userDao struct {
	db *gorm.DB
}

type UserDao interface {
	CreateUser(m *User) (string, error)
	GetUser(id string) (*User, error)
	UpdateUser(m *User) (*User, error)
	DeleteUser(id string) error
}
```

で、登録に関するメソッドが下記。

```go:[model/user.go]
func (n *userDao) CreateUser(m *User) (string, error) {
	m.ID = strings.Replace(uuid.New().String(), "-", "", -1)
	n.db.Create(&m)
	if n.db.Error != nil {
		return "", n.db.Error
	}
	return m.ID, nil
}
```

## 動作確認

[Postman](https://learning.getpostman.com)を使って確認。

### 登録

こんな curl を叩いて、

```
curl -X POST \
  http://localhost:8080/api/v1/users \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/json' \
  -H 'Postman-Token: fb182b6d-1ee3-4e1e-91ad-1c8b426e7dba' \
  -d '{
	"name": "さとう"
}'
```

こんなJSONが返る。

```
{
    "id": "7f8a46635aad48ccb668d8f1e6c0751b"
}
```

### 取得

こんな curl を叩いて、

```
curl -X GET \
  http://localhost:8080/api/v1/users/7f8a46635aad48ccb668d8f1e6c0751b \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/json' \
  -H 'Postman-Token: 0e933df9-5c71-4170-b888-7f4b1c368b51'
```

こんなJSONが返る。

```
{
    "ID": "7f8a46635aad48ccb668d8f1e6c0751b",
    "Name": "さとう"
}
```

# まとめ
テストコードを書いていないのと、やはり１機能のCRUDレベルではクリーンアーキテクチャと比べたときの効用は計り知れない。
ただ、チームメンバーが誰も実際にクリーンアーキテクチャで実装したことがない、かつ、プロジェクトとしてそこに時間を費やせない場合、とにかくスピーディーに、かつ、ある程度、機能毎に分業できることを考えると、いい構成かも。