Clean Architecture with Go

# 💡 はじめに
この記事は、クリーンアーキテクチャとGo言語を半年ほど勉強した人の知見をまとめたものです。[zenn](https://zenn.dev/88888888_kota/articles/1a5caac8d743b8)にも同じ内容を投稿しています。この記事の対象者としては、
* クリーンアーキテクチャ聞いたことあるけどGoでの実装方法？
といった方を想定しています。

自分自身も、完全に理解しているとは言い難いですが、ある程度知見が溜まってきたのでまとめておきます。Go言語でクリーンアーキテクチャを実現したい方の参考になれば幸いです。

# 🤔 クリーンアーキテクチャとは？
* システム設計ガイドラインの1つ
* メリット
	* 疎結合、関心の分離により変化に強い
	* テストが容易
* デメリット
	* コードが冗長
	* わかりにくい
# 🛠 実際に実装してみる
クリーンアーキテクチャに関しては、ネット上の記事を読んでもあまりピンとこないかもしれないので、**実際に実装**してみることあるいは**実際のコードを読む**ことが理解の近道だと思います。その際、参考になるレポジトリを以下に示します(他にもいろいろサンプルレポジトリが転がっています[githubで調べる](https://github.com/search?q=go+clean+architecture)と良いでしょう)。

https://github.com/bxcodec/go-clean-arch

https://github.com/zhashkevych/go-clean-architecture

https://github.com/eminetto/clean-architecture-go-v2

## 目標
この記事での目標は、**GoでクリーンアーキテクチャのテンプレをGithubに作る**こととします。テンプレを持っておくことで今後勉強する際に何かと役に立つかと思います。
完成形はこちら

https://github.com/yagikota/clean_architecture_with_go

### 使用技術
使用技術はこんな感じです
* 言語
	* Go
* RDBMS
	* MySQL
* ライブラリ
	* echo
	* SQLBoiler
### 作るもの
クリーンアーキテクチャの理解が目的なので学生情報を取得する簡単なAPIを作る。
API定義書は[こちら](https://github.com/yagikota/clean_architecture_with_go/blob/main/api_doc.yml)を https://editor.swagger.io/ に貼り付けて確認。

### 下準備
未完成なコード
```shell
git clone -b develop git@github.com:yagikota/clean_architecture_with_go.git
```
最低限のコードを用意してある
完成形のコードはこちら
```shell
git clone git@github.com:yagikota/clean_architecture_with_go.git
```
フォルダ構成
```
pkg
├── adapter(黄色)
│   └── http
│       ├── health_check.go
│       ├── router.go // エンドポイントを書く
│       └── student_handler.go // ハンドラーを書く
├── config
│   └── config.go // 環境変数の読み込みなどを書く
├── domain(緑色)
│   ├── model// model配下にSQLBoilerでmodelが自動生成される
│   │   ├── boil_queries.go
│   │   ├── boil_table_names.go
│   │   ├── boil_types.go
│   │   ├── boil_view_names.go
│   │   ├── mysql_upsert.go
│   │   └── students.go
│   ├── repository // DBへの操作をinterfaceで定義する
│   │   └── student_repository.go
│   └── service // repositoryとusecaseの橋渡し
│       └── student_service.go
├── infra(オレンジ色)
│   ├── db_conn.go // DBとのコネクションを張る
│   └── mysql
│       └── mysql.go // domain/repositoryで定義されているDBへの操作を具体的に書く
└── usecase
    ├── model // DBから自動生成したmodelを使いやすいように変換(抽象化)する
    │   └── student.go
    └── student_usecase.go // ごちゃごちゃ処理を書く
```
 ![](https://storage.googleapis.com/zenn-user-upload/22486ed6053e-20220824.png)
 https://little-hands.hatenablog.com/entry/2018/12/10/ddd-architecture より引用 
 
### 現状のコードの説明
* 現状のコードには、APIが１つ作られている。コードを実行し、`localhost:8080/v1/students`にアクセスすると、
 ```json
[
    {
        "id": 1,
        "name": "Yamada Ichirou",
        "age": 22,
        "class": 1
    },
    {
        "id": 2,
        "name": "Yamada Jirou",
        "age": 22,
        "class": 1
    },
  ...
    {
        "id": 10,
        "name": "Yamada Jurou",
        "age": 21,
        "class": 4
    }
]
 ```
が返ってくる。
以下では、現状のコードにAPIを1つ追加してみる。その過程で、コードの意味なども見ていく。
 
#### 実装
##### repository層
まず、`pkg/domain/repository/student_repository.go`から見ていく。
```go:pkg/domain/repository/student_repository.go
package repository

import (
	"context"

	"github.com/yagikota/clean_architecture_wtih_go/pkg/domain/model"
)

// Iはinterfaceを表現
type IStudentRepository interface {
	SelectAllStudents(ctx context.Context) (model.StudentSlice, error)
	SelectStudentByID(ctx context.Context, id int) (*model.Student, error) // 追加
}
```
ここでは、DBの各テーブルへの最低限の操作をinterfaceで定義する。今回は、DBに対して`SelectAllStudents`, `SelectStudentByID`という2つの操作を**定義するだけで具体的な処理は書かない**(具体的な処理は`pkg/infra/mysql/mysql.go`に書く)。
interfaceについて簡単に説明すると、**interfaceとは説明書(仕様書)** である。
```go
// 説明書
type IDummy interface {
	Method1() // 条件１
	Method2() // 条件２
}
```
例えば、上のinterfaceは、2つの条件`Method1()`と`Method2()`が書かれた説明書`IDummy`を表現している。
今回の場合だと、
```md
IStudentRepositoryの説明書
* IStudentRepositoryには以下のmethodが実装されています。
  * SelectAllStudents(ctx context.Context) (model.StudentSlice, error)
  * SelectStudentByID(ctx context.Context, id int) (*model.Student, error)
```
みたいな意味になる。interfaceを使うメリットはinfra層の部分で説明する。

##### infra層
次に、`pkg/infra/mysql/mysql.go`を見ていく。
```go:pkg/infra/mysql/mysql.go
// インターフェースを満たすstruct
type studentRepository struct {
	DB *sql.DB
}

// NewXXXXについては後ほど説明する
func NewRoomRepository(db *sql.DB) repository.IStudentRepository {
	return &studentRepository{
		DB: db,
	}
}

func (sr *studentRepository) SelectAllStudents(ctx context.Context) (model.StudentSlice, error) {
	// DBに対する具体的な操作
	return model.Students().All(ctx, sr.DB)
}

// 追加
func (sr *studentRepository) SelectStudentByID(ctx context.Context, studentID int) (*model.Student, error) {
	// DBに対する具体的な操作
	whereID := fmt.Sprintf("%s = ?", model.StudentColumns.ID)
	return model.Students(
		qm.Where(whereID, studentID),
	).One(ctx, sr.DB)
}
```
ここでは、まず、repository層で追加した`FindStudentByID`の**具体的な処理**を追加する。こうすることで、`*studentRepository`は先ほど`pkg/domain/repository/student_repository.go`で定義した`IStudentRepository`を満たすことになる。よって、**infra層とrepository層が`IStudentRepository`と言う説明書に基づいて繋がった**といえる。
このように、各層をinterfaceに基づいて接続してくことで、**疎な結合(interfaceさえ満たしていれば、片方の層の処理はもう片方の層の処理を気にしたくても実装できる)** を実現できる。テストする際も、目の前の層だけに着目すればいいのでテストしやすい。

##### service層
次に、`pkg/domain/service/student_service.go`を見ていく。
```go:pkg/domain/service/student_service.go
package service

import (
	"context"

	"github.com/yagikota/clean_architecture_wtih_go/pkg/domain/model"
	"github.com/yagikota/clean_architecture_wtih_go/pkg/domain/repository"
)

type IStudentService interface {
	FindAllStudents(ctx context.Context) (model.StudentSlice, error)
	FindStudentByID(ctx context.Context, id int) (*model.Student, error)
}

// インターフェースを満たすstruct
type studentService struct {
	repo repository.IStudentRepository
}

// NewXXXX(コンストラクタ)については後ほど説明する
func NewStudentService(sr repository.IStudentRepository) IStudentService {
	return &studentService{
		repo: sr,
	}
}

func (ss *studentService) FindAllStudents(ctx context.Context) (model.StudentSlice, error) {
	return ss.repo.SelectAllStudents(ctx)
}

func (ss *studentService) FindStudentByID(ctx context.Context, id int) (*model.Student, error) {
	return ss.repo.SelectStudentByID(ctx, id)
}
```
ここでは、**respository層での操作を組み合わせた処理**を行う。今回は、単純に処理を受け流しているだけ。
また、
```go
type studentService struct {
	repo repository.IStudentRepository
}
```
のようにservice層で定義するinterface(`IStudentService`)を満たすオブジェクト(`studentService`)の内部にはrepository層で定義したinterfaceが入っているので、repository層で定義したinterfaceを用いる場合
`return ss.repo.SelectAllStudents(ctx)`
`return ss.repo.SelectStudentByID(ctx, id)`
のように取り出す形になる(マトリョーシカ🪆みたいに)。
初期化のためのコンストラクタ` NewStudentService`も用意しておく。
他の層でも同じようなことを行う。
servie層の必要性に関しては[こちらの記事](https://christina04.hatenablog.com/entry/go-clean-architecture)がわかりやすい。

##### usecase層
次に、`pkg/usecase/student_usecase.go`を見ていく。
```go:pkg/usecase/student_usecase.go
import (
	"context"
	"github.com/yagikota/clean_architecture_wtih_go/pkg/usecase/model"
"github.com/yagikota/clean_architecture_wtih_go/pkg/domain/service"
)

type IStudentUsecase interface {
	FindAllStudents(ctx context.Context) (model.StudentSlice, error)
	FindStudentByID(ctx context.Context, id int) (*model.Student, error) // 追加
}

// インターフェースを満たすstruct
type studentUsecase struct {
	svc service.IStudentService
}

// NewXXXX(コンストラクタ)については後ほど説明する
func NewUserUsecase(ss service.IStudentService) IStudentUsecase {
	return &studentUsecase{
		svc: ss,
	}
}

func (su *studentUsecase) FindAllStudents(ctx context.Context) (model.StudentSlice, error) {
	msSlice, err := su.svc.FindAllStudents(ctx)
	if err != nil {
		return nil, err
	}

	sSlice := make(model.StudentSlice, 0, len(msSlice))
	for _, ms := range msSlice {
		sSlice = append(sSlice, model.StudentFromDomainModel(ms))
	}

	return sSlice, nil
}

// 追加
func (su *studentUsecase) FindStudentByID(ctx context.Context, id int) (*model.Student, error) {
	ms, err := su.svc.FindStudentByID(ctx, id)
	if err != nil {
		return nil, err
	}

	return model.StudentFromDomainModel(ms), nil
}1
```
usecae層では、service層から取ってきたデータを変換してadapter層に渡す処理を書く。今回は、`StudentFromDomainModel(ms)`の部分で変換をおこなっている。変換処理のロジックは`pkg/usecase/student_usecase.go`に書いてある。

##### adapter層
###### handler
次に、`pkg/adapter/http/student_handler.go`を見ていく。
```go:pkg/adapter/http/student_handler.go
// インターフェースを満たすstruct
type studentHandler struct {
	usecase usecase.IStudentUsecase
}

// コンストラクタ
func NewStudentHandler(su usecase.IStudentUsecase) *studentHandler {
	return &studentHandler{
		usecase: su,
	}
}

func (sh *studentHandler) FindAllStudents() echo.HandlerFunc {
	return func(c echo.Context) error {
		ctx := c.Request().Context()
		student, err := sh.usecase.FindAllStudents(ctx)
		if err != nil {
			return c.JSON(http.StatusInternalServerError, err.Error())
		}
		return c.JSON(http.StatusOK, student)
	}
}

// 追加
func (sh *studentHandler) FindStudentByID() echo.HandlerFunc {
	return func(c echo.Context) error {
		ctx := c.Request().Context()
		studentID, err := strconv.Atoi(c.Param("student_id"))
		if err != nil {
			return c.JSON(http.StatusBadRequest, err.Error())
		}
		student, err := sh.usecase.FindStudentByID(ctx, studentID)
		if err != nil {
			return c.JSON(http.StatusInternalServerError, err.Error())
		}
		return c.JSON(http.StatusOK, student)
	}
}
```
adapter層のhandlerでは、リクエストを受け取って、後続の処理に引き渡し、結果をレスポンスとして返す。

###### rooter
```go:pkg/adapter/http/router.go
const (
	apiVersion      = "/v1"
	healthCheckRoot = "/health_check"
	// student系
	studentsAPIRoot = apiVersion + "/students"
	studentIDParam  = "student_id" // 追加
)

func InitRouter() *echo.Echo {
	e := echo.New()
	e.Use(
		middleware.Logger(),
		middleware.Recover(),
	)

	// ヘルスチェック
	healthCheckGroup := e.Group(healthCheckRoot)
	{
		relativePath := ""
		healthCheckGroup.GET(relativePath, healthCheck)
	}

	// student
	// DI
	mySQLConn := infra.NewMySQLConnector()
	studentRepository := mysql.NewStudentRepository(mySQLConn.Conn)
	studentService := service.NewUserService(studentRepository)
	studentUsecase := usecase.NewUserUsecase(studentService)

	studentGroup := e.Group(studentsAPIRoot)
	{
		handler := NewStudentHandler(studentUsecase)
		// v1/students
		relativePath := ""
		studentGroup.GET(relativePath, handler.FindAllStudents())
		// v1/students/{student_id}
		relativePath = fmt.Sprintf("/:%s", studentIDParam)
		studentGroup.GET(relativePath, handler.FindStudentByID())
	}

	return e
}

```
adapter層のrooterでは、エンドポイントの登録やDIを行う。以下では、DI(Dependancy Injection)の説明をする。
```go
mySQLConn := infra.NewMySQLConnector()
studentRepository := mysql.NewStudentRepository(mySQLConn.Conn)
studentService := service.NewUserService(studentRepository)
studentUsecase := usecase.NewUserUsecase(studentService)
handler := NewStudentHandler(studentUsecase)
```
この部分が、DIをしている。日本語では、依存性(依存するオブジェクト)の注入と言われる。
```go
studentRepository := mysql.NewStudentRepository(mySQLConn.Conn)
studentService := service.NewUserService(studentRepository)
studentUsecase := usecase.NewUserUsecase(studentService)
handler := NewStudentHandler(studentUsecase)
```
クリーンアーキテクチャでは、`handler→usecase→service→repository`と言う依存関係があるので、handlerの初期化にはusecaseが、usecaseの初期化にはseriviceが、serviceの初期化にはrepositoryが必要になってくる。その際、使用するのが各ファイルに実装してある`NewXXXX`と言う関数(コンストラクタ)である。コンストラクタの返り値はinterfaceにしておくことで、各層をinterfaceで接続でき、疎結合を実現できる。**各層で定義したコンストラクタを呼び出してhandlerを作成し、後続の処理に渡す**。要は、handlerではマトリョーシカ🪆を作り、🪆を一つ開けてusecaseに渡しserivice, repositoryでも同じ感じに渡していくイメージ。

# 📔 参考文献
アーキテクチャ

https://little-hands.hatenablog.com/entry/2018/12/10/ddd-architecture

https://christina04.hatenablog.com/entry/go-clean-architecture

https://zenn.dev/maru44/articles/b9e07e91a0ea77

https://www.amazon.co.jp/gp/product/4863543727/

DI

https://qiita.com/ogady/items/34aae1b2af3080e0fec4#%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%82%E3%81%AE



# 🍀 まとめ
いかがでしょうか？おそらく参考になった方もいれば全然参考にならたかった方もいると思います。また、この記事の内容が正解とも限りません。なので、色々な記事やサンプルコードを読み、そして、実際に手を動かしてみることが大切だと思います。
間違っている点やなどあればご指摘いただけるとありがたいです。適宜修正いたします😀。
ソースコード再掲

https://github.com/yagikota/clean_architecture_with_go