Go言語でTODOアプリwithクリーンアーキテクチャ

この記事ではクリーンアーキテクチャを学びながらGoで簡単なTODOアプリを作成する記事です。勉強のまとめをした物なので間違っている点があればご指摘をお願いします。

# クリーンアーキテクチャ
### クリーンアーキテクチャとは
ソフトウェアをレイターごとに役割を切り分けることにより関心を分離させることができるという考え方です。
翻訳版は[こちら](https://blog.tai2.net/the_clean_architecture.html)

### 採用する目的

1. フレームワーク独立
フレームワークを道具として使うことでアプリケーションがフレームワークに依存せず、制約を受けない

2. テスト可能
レイヤー同士が独立しているのでテストがしやすい。

3. UI独立
ビジネスルールを変えずにUIを変更できる。

4. データベース独立
postgresやMySqlなどのデータベースの変更が容易である。

5. 外部機能独立
ビジネスルールは外部レイヤーに依存しない。

### レイヤー

説明するのは４つだが４つでなくても良いです。

![](https://blog.tai2.net/images/CleanArchitecture.jpg)

1. Entities

ビジネスルールのためのデータ構造や関数

2. Use Case

アプリケーション固有のビジネスルールを定義する。エンティティとのデータをやり取りする。

3. Interfce Adapters

アダプターの集合。内側にある層から外側の層にデータを変換して受け渡す。逆に外側から内側にデータを受け渡す。

4. Frameworks & Drivers

フレームワークやツールから構成される。

### レイヤー間の規則
依存ルール。各レイヤーは内側のレイヤーのことしか知らず、外側のレイヤーについては知らない。

>
内側の円は、外側の円についてなにも知ることはない。とくに、外側の円で宣言されたものの名前を、内側の円から言及してはならない。これは、関数、クラス、変数、あるいはその他、名前が付けられたソフトウェアのエンティティすべてに言える。
同様に、外側の円で使われているデータフォーマットを内側の円で使うべきではない。とくに、それらのフォーマットが、外側の円でフレームワークによって生成されているのであれば。外側の円のどんなものも、内側の円に影響を与えるべきではないのだ。

# 実装するアプリケーション
作成したアプリケーションのコードは[こちら](https://github.com/kikils/golang-todo)です。
TODOとユーザーを使った単純なアプリケーションです。WebAPIを提供します。
ユーザーのCRUD, TODOのCRUDを行います。

#### 開発環境
Docker 19.03.12
docker-compose 1.26.2
Go 1.13
PostgreSQL
github.com/lib/pq 1.8.0

### ディレクトリ構成
````
├── domain
│   └── model
│       ├── todo.go
│       └── user.go
├── infrastructure
│   ├── router.go
│   ├── sqlhandler.go
│   └── sqlhandler_test.go
├── interfaces
│   ├── controllers
│   │   ├── todoController.go
│   │   └── userController.go
│   └── database
│       ├── sqlhandler.go
│       ├── todoRepository.go
│       └── userRepository.go
├── main.go
└── usecase
    ├── todoInteractor.go
    ├── todoRepository.go
    ├── userInteractor.go
    └── userRepository.go
````
とします。アーキテクチャ図とディレクト構成の関係は次のようになっています。

| 依存の方向| レイヤー | ディレクトリ名(パッケージ名) |
| ---------- | ------- | -------------- |
| - | Entities | domain(model) |
| ↑ | Use cases | usecase(usecase) |
| ↑ | interfaces | interfaces(controllers, database) |
| ↑ | Frameworks & Drivers | infrastracture(infrastructure) |

### データの流れ

- DB操作
`Use case(Interactor)`→`interface(Repository)`→`infrastructure(Sqlhandler)`

- リクエスト処理
アクセス→`infrastructure(handler)`→`interfaces(controller)`→httpリスポンス

### Entitiesレイヤ
`User`, `Todo`はビジネスルールのためのデータ構造なのでここで定義します。また、今回は追加していないユーザーの名前の文字数制限などはここで定義する必要があります。`User`と`Todo`は次のようになります。

```golang:/domain/model/user.go
package model

type User struct {
	ID        int    `json:"id"`
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
}

type Users []User
```

```golang:/domain/model/todo.go
package model

import "time"

type Todo struct {
	ID      int       `json:"id"`
	Title   string    `json:"title"`
	Note    string    `json:"note"`
	DueDate time.Time `json:"due_date"`
	UserID  int       `json:"user_id"`
}

type Todos []Todo
```

### infrastructureレイヤ
このレイヤでは外部のツールであるPostgresとの接続を行います。

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

import (
	"database/sql"
	"fmt"

	"github.com/kikils/golang-todo/interfaces/database"
	_ "github.com/lib/pq" // postres driver
)

type Sqlhandler struct {
	DB *sql.DB
}

func NewSqlhandler() *Sqlhandler {
	connStr := "postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable"
	db, err := sql.Open("postgres", connStr)
	if err != nil {
		return nil
	}

	err = db.Ping()
	if err != nil {
		fmt.Println("here")
		return nil
	}

	return &Sqlhandler{db}
}

func (handler *Sqlhandler) Execute(statement string, args ...interface{}) (database.Result, error) {
	res := SqlResult{}
	result, err := handler.DB.Exec(statement, args...)
	if err != nil {
		return res, err
	}
	res.Result = result
	return res, nil
}

func (handler *Sqlhandler) Query(statement string, args ...interface{}) (database.Row, error) {
	rows, err := handler.DB.Query(statement, args...)
	if err != nil {
		return new(SqlRow), err
	}
	row := new(SqlRow)
	row.Rows = rows
	return row, nil
}

type SqlResult struct {
	Result sql.Result
}

func (r SqlResult) LastInsertId() (int64, error) {
	return r.Result.LastInsertId()
}

func (r SqlResult) RowsAffected() (int64, error) {
	return r.Result.RowsAffected()
}

type SqlRow struct {
	Rows *sql.Rows
}

func (r SqlRow) Scan(dest ...interface{}) error {
	return r.Rows.Scan(dest...)
}

func (r SqlRow) Next() bool {
	return r.Rows.Next()
}

func (r SqlRow) Close() error {
	return r.Rows.Close()
}
```

次に実際にSQlとのデータのやり取りを行う処理を`interfaces/database`に書きます。

### interfaces/databaseレイヤ
このレイヤには`UserRepository`と`TodoRepository`を宣言します。
例として`userRepository.go`を示します。`SQL`を叩いていることがわかります。

```golang:/interfaces/database/userRepository.go
package database

import (
	"github.com/kikils/golang-todo/domain/model"
)

type UserRepository struct {
	Sqlhandler
}

func (repo *UserRepository) Store(u model.User) (id int, err error) {
	row, err := repo.Sqlhandler.Query(
		"INSERT INTO users (FirstName, LastName) VALUES ($1,$2) RETURNING id;", u.FirstName, u.LastName,
	)

	if err != nil {
		return
	}
	for row.Next() {
		if err := row.Scan(&id); err != nil {
			return -1, err
		}
	}
	return
}
... 略
```

`UserRepository`に`infrastructure`レイヤで定義した`Sqlhandler`を埋め込んでいます。
しかし、先ほど説明したように内側の層は外側の層の物を使用してはいけないので新しく`Sqlhandler`を`interface`レイヤに定義します。

```golang:/interfaces/database/sqlhandler.go
package database

type Sqlhandler interface {
	Execute(string, ...interface{}) (Result, error)
	Query(string, ...interface{}) (Row, error)
}

type Result interface {
	LastInsertId() (int64, error)
	RowsAffected() (int64, error)
}

type Row interface {
	Scan(...interface{}) error
	Next() bool
	Close() error
}
```

`infrastructure`レイヤの`Sqlhandler`は`Execute(string, ...interface{}) (Result, error)` `Query(string, ...interface{}) (Row, error)`をもっているのでこれを満たすことができます。
`interface`レイヤに`Sqlhandler` `Result` `Row`を定義することで`interface`レイヤは`infrastructure`レイヤに依存することはなくなるのです。(Goのインターフェースについては[ここ](https://medium.com/since-i-want-to-start-blog-that-looks-like-men-do/%E5%88%9D%E5%BF%83%E8%80%85%E3%81%AB%E9%80%81%E3%82%8A%E3%81%9F%E3%81%84interface%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9-golang-48eba361c3b4))

### Usecaseレイヤ
このレイヤでは`interface`レイヤからのInput portの役割、`interfaces/controllers`へのGatewayの役割をしている。

```golang:/usecase/userInteractor.go
package usecase

import "github.com/kikils/golang-todo/domain/model"

type UserInteractor struct {
	UserRepository UserRepository
}

func (interactor *UserInteractor) Add(u model.User) (id int, err error) {
	id, err = interactor.UserRepository.Store(u)
	return
}
...略
```

このように`Repository`をもつ`Interactor`を定義する。`Repository`は`interfaces/database`レイヤに存在するので、この層でも先ほどと同じようにインターフェースを定義することで依存関係を解決する。そのため`Usecase`レイヤでも`Repository`を定義する。

```golang:/usecase/userRepository.go
package usecase

import "github.com/kikils/golang-todo/domain/model"

type UserRepository interface {
	Store(model.User) (int, error)
	Update(user model.User) (id int, err error)
	Delete(userID int) (err error)
	FindById(int) (model.User, error)
	FindAll() (model.Users, error)
}
```

### interfaces/controllersレイヤ
ここのレイヤではリクエストに対するルーティングを実装します。

```golang:/interfaces/controllers/userController.go
package controllers

import (
	"encoding/json"
	"io/ioutil"
	"net/http"

	"github.com/kikils/golang-todo/domain/model"
	"github.com/kikils/golang-todo/interfaces/database"
	"github.com/kikils/golang-todo/usecase"
)

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(w http.ResponseWriter, r *http.Request) {
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		ResponseError(w, http.StatusInternalServerError, err.Error())
		return
	}

	var user model.User
	if err := json.Unmarshal(b, &user); err != nil {
		ResponseError(w, http.StatusBadRequest, err.Error())
		return
	}
	id, err := controller.Interactor.Add(user)
	if err != nil {
		ResponseError(w, http.StatusBadRequest, err.Error())
		return
	}
	ResponseOk(w, id)
}
...略
```

`NewUserController`で`database.Sqlhandler`を引数に持つので`Interactor`を返して`Usecase`レイヤと`infrastructure/database`を紐づけられます。
次にルーティングを実装します。

### infrastructureレイヤ
リクエストは外部から来るので最外殻の`infrastructure`レイヤにルーティングを実装します。

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

import (
	"encoding/json"
	"log"
	"net/http"

	"github.com/kikils/golang-todo/interfaces/controllers"
)

func SetUpRouting() *http.ServeMux {
	mux := http.NewServeMux()

	sqlhandler := NewSqlhandler()
	userController := controllers.NewUserController(sqlhandler)
	todoController := controllers.NewTodoController(sqlhandler)

	mux.HandleFunc("/user/create", func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodPost:
			userController.Create(w, r)
		default:
			ResponseError(w, http.StatusNotFound, "")
		}
	})

	mux.HandleFunc("/user/get", func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodPost:
			userController.Show(w, r)
		default:
			ResponseError(w, http.StatusNotFound, "")
		}
	})
...略
```

`SetUpRouting()`で`Sqlhandler`を定義し`Controller`に渡してあげることで各レイヤの依存関係を保つことができます。

その後、`main.go`に

```golang:/main.go
package main

import (
	"log"
	"net/http"

	"github.com/kikils/golang-todo/infrastructure"
)

func main() {
	mux := infrastructure.SetUpRouting()
	log.Fatal(http.ListenAndServe(":8080", mux))
}
```

として`SetUpRouting()`を呼び出して実装は終了です。

# 最後に
今回はクリーンアーキテクチャを用いてTODOアプリを作成してみました。最初はクリーンアーキテクチャがとっつきにくかったです。実装してみて思ったのは、クリーンアーキテクチャにすることで同じようなコードを複数書く必要があることや、コード補完がきかないので実装のしにくさに気づきました。けれども、DBなどから独立しているので簡単に変更可能だと実感できました。

# 参考にしたもの
https://qiita.com/ogady/items/34aae1b2af3080e0fec4
https://qiita.com/hirotakan/items/698c1f5773a3cca6193e
https://github.com/eminetto/clean-architecture-go
https://github.com/cohhei/go-to-the-handson/tree/master/04
https://yyh-gl.github.io/tech-blog/blog/go_web_api/