Go で Clean Architecture 入門


# はじめに

Clean Architecture とは DB やフレームワークなどの外部技術に依存せず、ソフトウェアの中核部分（ビジネスを表現する部分）の独立性を高めることで、外部技術の変更に強いソフトウェアを実現することを目的とする。

以下は Clean Architecture の概念図です。図を見ながらポイントを解説していきます。

![clean-architecture.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125540/7a751b6f-b2f3-f606-e28a-a60b781a92d8.jpeg)

## 「関心事の分離」を目的としたアーキテクチャ

- ソフトウェアをレイヤーに分割。各レイヤーの責務を明確にし、レイヤー間の依存関係を制限することで、関心事を分離する
- データベースとの連携を責務とするレイヤー、API 通信を責務とするレイヤー、ビジネスロジックを責務とするレイヤーなど
- 関心事が分離されることで、他レイヤーの変更による影響を最小限にし、変更に強いアーキテクチャを実現する

## 4 つのレイヤーで構成される（4 つである必要はない。依存方向のルールを遵守する事が重要）

- Frameworks & Drivers
  - フレームワークやデータベースに依存する実装を担当
- Interface Adapters
  - Frameworks & Drivers と Application Business Rules の間のフォーマット変換を担当
  - 変換することで、Application Business Rules への外部技術の流入を防ぐ
- Application Business Rules
  - ビジネスロジックを担当
  - ソフトウェアで何が出来るかを実装する
  - （例）人事管理システムの場合、「ユーザーを登録する」「ユーザーを削除する」などを表現する
- Enterprise Business Rules
  - ソフトウェアの中核となる部分
  - エンティティなど最重要ビジネスルールの実装がされる
  - （例）人事管理システムの場合、「ユーザー」を表現するエンティティを実装する

## レイヤー間の依存方向は外側（下位レイヤー）から内側（上位レイヤー）に向かう

- 上位レイヤーは下位レイヤーの実装に依存しない（下位レイヤーの関数や構造体を参照してはいけない）
- 上位レイヤーが下位レイヤーに依存すると、上位レイヤーの安定性が下がってしまうため
  - 上位レイヤー：ビジネスを表現したソフトウェアの中核になるため、強固な安定性が求められる
  - 下位レイヤー：外部の技術に依存する定義が多いため、変更が発生しやすい（変更が容易に出来る状態に保つ）
- ↓ コンポーネントレベルの依存関係図。下位レイヤーから上位レイヤーに依存方向が向かう、且つ、レイヤー間の連携は抽象化されたインターフェースを介して行われる（変化しやすい具象への依存を避ける）。
  ![Sequence](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125540/e7c3554a-4024-e71e-28e7-de008df15042.png)

以降は、サンプルコードを交えながら、解説していきます。

# Architecture

今回は、以下の構成で実装していきます。

![architecture](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125540/11e59808-1b11-03d1-e36b-2fe7e1a43442.png)

```
.
├── adapter
│   ├── controller
│   │   ├── user_handler.go
│   │   └── user_handler_mapper.go
│   └── gateway
│       ├── user_repository_impl.go
│       └── user_repository_mapper.go
├── domain
│   └── entity
│       └── user.go
├── infrastructure
│   └── db
│       └── xxx.go
└── usecase
    ├── user_repository_port.go
    ├── user_usecase_impl.go
    ├── user_usecase_mapper.go
    └── user_usecase_port.go
```

## Infrastructure

フレームワークやデータベースに依存する実装を担当する。  
データベース接続やマイグレーション、REST API の仕様定義など。

```go
// db

type DBEnv struct {
	Name      string `env:"MYSQL_DATABASE"`
	User      string `env:"MYSQL_USER"`
	Password  string `env:"MYSQL_PASSWORD"`
	Address   string `env:"MYSQL_HOST"`
	Collation string `env:"MYSQL_COLLATION"`
}

func NewDB() *bun.DB {
	cfg := DBEnv{}
	if err := env.Parse(&cfg); err != nil {
		panic(err)
	}

	c := mysql.Config{
		User:                 cfg.User,
		Passwd:               cfg.Password,
		Net:                  "tcp",
		Addr:                 cfg.Address,
		DBName:               cfg.Name,
		Collation:            cfg.Collation,
		ParseTime:            true,
		AllowNativePasswords: true,
	}

	_db, err := sql.Open("mysql", c.FormatDSN())
	if err != nil {
		panic(err)
	}
	if err = _db.Ping(); err != nil {
		panic(err)
	}

	db := bun.NewDB(_db, mysqldialect.New())
	return db
}
```

## Adapter

Web API やデータベースなど外部とのやり取りを担当する。  
外部フォーマット ⇔ 上位レイヤーフォーマットの変換もこのレイヤーで行う。

外部とのやり取り・仕様に関する詳細を本レイヤーに隠蔽し、外部仕様変更に伴う上位レイヤーへの影響を最小限にする事が責務。  
本アーキテクチャでは、Controller が Web API とのやり取りを担当し、Gateway がデータベースとのやり取りを担当する。

### Controller

Web API とのやり取りを担当する。  
リクエストデータを Use Case で扱うフォーマットに変換の上、Use Case レイヤーに連携する。レスポンスでは、Use Case レイヤーの出力データを Web API フォーマットに変換して返却する。  
Use Case レイヤーとの連携は、Use Case Port （インターフェース）を介して行う。

<font color="gray">※ クリーンアーキテクチャの概念図（Flow of control の部分）やコンポーネントレベルの依存関係図に従うと、Use Case からの出力は Presenter で処理するべきですが、今回は Controller で処理しています。Presenter は UI を意識したフォーマット変換を行うイメージであり、今回の Web API レスポンスとは責務が異なると考えたため。</font>

```go
// user_handler.go

func (h *UserHandler) AddUser(w http.ResponseWriter, r *http.Request) {
	req, err := ToDTO(r.Body) // Web API フォーマットから Use Case フォーマットへの変換
	if err != nil {
		HttpError(w, err)
		return
	}

	result, err := h.usecase.AddUser(r.Context(), req)
	if err != nil {
		HttpError(w, err)
		return
	}

	h.HandleOK(w, FromDTO(result)) // Use Case フォーマットから Web API フォーマットへの変換
}
```

Web API フォーマットと Use Case フォーマットの変換は、Controller Mapper を参照する。

### Controller Mapper

Web API フォーマットと Use Case フォーマットの変換を行う。  
アプリケーションエラーから HTTP エラーへの変換も行う。（詳細は [Error Strategy](#error-strategy) を参照）

<font color="gray">※ 詳細省きますが、API レスポンスは OpenAPI で生成したモデルを参照しています<font>

```go
// user_handler_mapper.go

// リクエストを Use Case フォーマットに変換
func ToDTO(
	body io.ReadCloser,
) (*usecase.User, *pkgErr.ApplicationError) {
	var dto usecase.User
	if err := json.NewDecoder(body).Decode(&dto); err != nil {
		return nil, pkgErr.NewApplicationError(err.Error(), pkgErr.LevelWarn, pkgErr.CodeBadRequest)
	}
	return &usecase.User{
		ID:        dto.ID,
		FirstName: dto.FirstName,
		LastName:  dto.LastName,
		Age:       dto.Age,
	}, nil
}

// Use Case フォーマットを API レスポンスに変換
func FromDTO(
	dto *usecase.User,
) *User {
	return &User{
		Id:        dto.ID,
		FirstName: dto.FirstName,
		LastName:  dto.LastName,
		Age:       int(dto.Age),
	}
}
```

### Gateway

データベースとのやり取りを担当する。Repository Port（インターフェース）の実装。  
Use Case レイヤーから連携された Entity を ORM モデルに変換し、データベースとのやり取りを行う。反対に、Use Case レイヤーへの返却時は、ORM モデルを Entity に変換して返却する。

```go
// user_repository_impl.go

func (u *UserRepositoryImpl) Save(ctx context.Context, entity *entity.User) (*entity.User, *pkgErr.ApplicationError) {
	tx := ctx.Value(TX_KEY).(*bun.Tx)

	user := FromEntity(entity) // Entity を ORM モデルに変換
	if _, err := tx.NewInsert().Model(user).Exec(ctx); err != nil {
		return nil, RepositoryError(err)
	}
	return user.ToEntity(), nil // ORM モデルを Entity に変換
}
```

ORM モデルと Entity の変換は、Gateway Mapper を参照する。

### Gateway Mapper

ORM モデルと Entity の変換を行う。  
データベースエラーからアプリケーションエラーへの変換も行う。（詳細は [Error Strategy](#error-strategy) を参照）

```go
// user_repository_mapper.go

// ORM モデル
type User struct {
	ID        string    `bun:"id,pk,type:uuid,default:gen_random_uuid()"`
	FirstName string    `bun:"first_name,notnull"`
	LastName  string    `bun:"last_name,notnull"`
	Age       int32     `bun:"age,notnull"`
	CreatedAt time.Time `bun:"created_at,nullzero,notnull,default:current_timestamp"`
	UpdatedAt time.Time `bun:"updated_at,nullzero,notnull,default:current_timestamp"`
}


// ORM モデルを Entity に変換
func (u *User) ToEntity() *entity.User {
	return &entity.User{
		ID:        u.ID,
		FirstName: u.FirstName,
		LastName:  u.LastName,
		Age:       u.Age,
		CreatedAt: u.CreatedAt,
		UpdatedAt: u.UpdatedAt,
	}
}


// Entity を ORM モデルに変換
func FromEntity(
	entity *entity.User,
) *User {
	return &User{
		ID:        entity.ID,
		FirstName: entity.FirstName,
		LastName:  entity.LastName,
		Age:       entity.Age,
		CreatedAt: entity.CreatedAt,
		UpdatedAt: entity.UpdatedAt,
	}
}
```

## Use Case

ビジネスロジック（ソフトウェアで何が出来るかの表現）を担当する。Use Case Port（インターフェース）の実装。  
Use Case フォーマットから Entity への変換を行い、Repository に渡す。Repository との連携は、Repository Port を介して行う。

```go
// user_usecase_port.go

type UserUsecase interface {
	AddUser(ctx context.Context, dto *User) (*User, *pkgErr.ApplicationError)
}
```

```go
// user_usecase_impl.go

func (u *UserUsecaseImpl) AddUser(
	ctx context.Context,
	dto *User,
) (*User, *pkgErr.ApplicationError) {
	entity, err := u.userRepository.Save(ctx, dto.ToEntity()) // Use Case フォーマットを Entity に変換
	if err != nil {
		return nil, err
	}
	return FromEntity(entity), nil // Entity を Use Case フォーマットに変換
}
```

Use Case フォーマットと Entity の変換は、Use Case Mapper を参照する。

### Use Case Mapper

Use Case フォーマット と Entity の変換を行う。

```go
// user_usecase_mapper.go

// Use Case フォーマット
type User struct {
	ID        string
	FirstName string
	LastName  string
	Age       int32
}

// Use Case フォーマットを Entity に変換
func (u *User) ToEntity() *entity.User {
	return &entity.User{
		FirstName: u.FirstName,
		LastName:  u.LastName,
		Age:       u.Age,
	}
}

// Entity を Use Case フォーマットに変換
func FromEntity(
	entity *entity.User,
) *User {
	return &User{
		ID:        entity.ID,
		FirstName: entity.FirstName,
		LastName:  entity.LastName,
		Age:       entity.Age,
	}
}
```

「[レイヤー間の依存方向は外側（下位レイヤー）から内側（上位レイヤー）に向かう](#レイヤー間の依存方向は外側下位レイヤーから内側上位レイヤーに向かう)」で述べた通り、依存方向を下位レイヤー → 上位レイヤーに限定する必要があるため、Repository Port は （Gateway 側ではなく）Use Case 側に定義。

```go
// user_repository_port.go

type UserRepository interface {
	Save(ctx context.Context, e *entity.User) (*entity.User, *pkgErr.ApplicationError)
}
```

## Domain

ビジネスルールを表現し、どのレイヤーにも依存しないソフトウェアの中核となる部分。

### Entity

```go
// entity/user.go

// Entity
type User struct {
	ID        string
	FirstName string
	LastName  string
	Age       int32
	CreatedAt time.Time
	UpdatedAt time.Time
}
```

# Error Strategy

アプリケーション独自のカスタムエラーを定義。外部エラーをカスタムエラーにマッピングすることで、外部技術の詳細を隠蔽する方針。
メッセージ、レベル、コードの 3 要素で構成され、レベルとコードによってエラーの重要度を表現し、よしなにハンドリングする。

データベースエラーをカスタムエラーに変換し、上位レイヤーへの詳細の流入を防いだり、カスタムエラーを HTTP エラーに変換し、適切なレスポンスを返却する用途で利用する。

```go
// Custom Error

type ErrorLevel int8

const (
	_ ErrorLevel = iota
	LevelInfo
	LevelWarn
	LevelError
)

type ErrorCode int

const (
	_ ErrorCode = iota
	CodeBadRequest
	CodeNotFound
	CodeDuplicate
	CodeInternalServerError
)

type ApplicationError struct {
	message string
	level   ErrorLevel
	code    ErrorCode
}

func (e *ApplicationError) Error() string {
	return e.message
}

func (e *ApplicationError) Level() ErrorLevel {
	return e.level
}

func (e *ApplicationError) Code() ErrorCode {
	return e.code
}

func NewApplicationError(message string, level ErrorLevel, code ErrorCode) *ApplicationError {
	return &ApplicationError{
		message: message,
		level:   level,
		code:    code,
	}
}

```

## Database Error Mapping

```go
// user_repository_mapper.go

func RepositoryError(err error) *pkgErr.ApplicationError {
	switch err {
	case sql.ErrNoRows:
		return pkgErr.NewApplicationError(err.Error(), pkgErr.LevelWarn, pkgErr.CodeNotFound)
	default:
		return pkgErr.NewApplicationError(err.Error(), pkgErr.LevelError, pkgErr.CodeInternalServerError)
	}
}
```

### Usage

```go
// user_repository_impl.go

func (u *UserRepositoryImpl) Save(ctx context.Context, entity *entity.User) (*entity.User, *pkgErr.ApplicationError) {
	tx := ctx.Value(TX_KEY).(*bun.Tx)

	user := FromEntity(entity)
	if _, err := tx.NewInsert().Model(user).Exec(ctx); err != nil {
		return nil, RepositoryError(err) // データベースエラーをカスタムエラーに変換
	}
	return user.ToEntity(), nil
}
```

## HTTP Error Mapping

```go
// user_handler_mapper.go

func NotFoundError(w http.ResponseWriter, err *pkgErr.ApplicationError) {
	setHeaderContentType(w)
	w.WriteHeader(http.StatusNotFound)
	json.NewEncoder(w).Encode(Error{
		Code:    http.StatusNotFound,
		Message: err.Error(),
	})
}

func InternalServerError(w http.ResponseWriter, err *pkgErr.ApplicationError) {
	setHeaderContentType(w)
	w.WriteHeader(http.StatusInternalServerError)
	json.NewEncoder(w).Encode(Error{
		Code:    http.StatusInternalServerError,
		Message: err.Error(),
	})
}

func HttpError(w http.ResponseWriter, err *pkgErr.ApplicationError) {
	switch err.Code() {
	case pkgErr.CodeNotFound:
		NotFoundError(w, err)
	default:
		InternalServerError(w, err)
	}
}
```

### Usage

```go
// user_handler.go

func (h *UserHandler) AddUser(w http.ResponseWriter, r *http.Request) {
	req, err := ToDTO(r.Body)
	if err != nil {
		HttpError(w, err) // カスタムエラーを HTTP エラーに変換
		return
	}

	result, err := h.usecase.AddUser(r.Context(), req)
	if err != nil {
		HttpError(w, err) // カスタムエラーを HTTP エラーに変換
		return
	}

	h.HandleOK(w, FromDTO(result))
}
```

# まとめ

外部技術への依存やレイヤー間の依存を制限し、関心事を分離することで、ソフトウェアの中核部分（ビジネスを表現する部分）の独立性が高まる。その結果、変更に強いソフトウェアを実現できる点は、とても魅力的だと感じました。新しく外部との連携を行う場合（例えば、gRPC の口を増やしたり）や、外部技術を別のものに置き換える場合にも、その影響範囲を限定できるのは、変更容易性の観点からも良いと思いました。  
ただ、その反面、多少冗長的になり、実装コストが増加することも否めないのかなと。

Clean Architecture の核となる考え方は踏襲しつつ、実装コストとのバランスを考慮しながら、よしなにアレンジするのが良さそうです。

# 参考

- [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
- [Clean Architecture 達人に学ぶソフトウェアの構造と設計](https://www.amazon.co.jp/Clean-Architecture-%E9%81%94%E4%BA%BA%E3%81%AB%E5%AD%A6%E3%81%B6%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%81%AE%E6%A7%8B%E9%80%A0%E3%81%A8%E8%A8%AD%E8%A8%88-Robert-C-Martin/dp/4048930656)