Go で Clean Architecture を実践する

# 概要

手探りしながら、 Golang でクリーンアーキテクチャを実装しました。
今後のために残しておきます。こうしたほうがいいな、って思ったら適宜更新していきたい。

ツッコミや、個人的にはこう思う、みたいなの大歓迎です。

# クリーンアーキテクチャとは

ここで新しく説明するよりも、[このQiita](https://qiita.com/nrslib/items/a5f902c4defc83bd46b8)を読んだほうが良いので割愛します。

この図だけないとイメージしづらいので持ってきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56770/40920d33-0e0d-db00-88b9-a2acda8c2aa1.png)


# 実践

## パッケージ・ディレクトリ構造

肝は依存関係をコントロールすることにあるので、円毎にディレクトリを用意しました。
また、usecase は単独で切り出し、中心に考えます。

```
.
├── adapters        # 緑色
├── app             # 赤色
├── domain          # 黄色
├── external        # 青色
└── usecases        # 赤色（定義）
```

これを元に、ファイルを順番に作っていきます。

## ユースケースの抽出

今回はよく見かける「**ユーザ登録**」を例に考えていきます。
この時点では処理の内容を深堀りする必要はないので、大雑把なパラメタと名前だけ決めます。

ユースケース名 | 処理内容
---|---
サインアップ | メールアドレスとパスワードを受け取ってユーザを登録する

決まったら、usecases 配下に interface を配置します。

```
.
├── adapters
├── app
├── domain
├── external
└── usecases
    └── sign_up_usecase.go
```

ソースコードはこちら。
UseCase の Interface と、Input / Output を定義しておきます。

```golang:usecases/sign_up_usecase.go
package usecases

type ISignUpUseCase interface {
        SignUp(input SignUpUseCaseInput) (output *SignUpUseCaseOutput, err error)
}

type SignUpUseCaseInput struct {
        Email    string
        Password string
}

type SignUpUseCaseOutput struct {
}
```

## UseCase内の処理を考える

サインアップ内で何をするか考えます。一旦はシンプルに以下とします。

* メールアドレスが他のユーザに登録されていないかチェックする
* ユーザを保存する

## ドメインを定義する

~~ドメインがよくわかってないので、~~処理内容から、**とりあえず重要そうなモノ**を取り出します。

* `ユーザ`を保存する

これをドメインとして定義します。

```
.
├── adapters
├── app
├── domain
│   └── user
│       └── user.go
├── external
└── usecases
    └── sign_up_usecase.go
```

ドメインはこのアプリケーションにとって最重要なものであるので、パッケージを専用で切ってみます。
ソースコードはこちら。

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

type User struct {
        Email    string
        Password string
}
```

## UseCaseの実装準備

UseCaseの実装をしていきます。赤色の実装は app の下ですね。 実装として、 Interactor を用意します。

```
.
├── adapters
├── app
│   └── interactors
│       └── sign_up_interactor.go
├── domain
│   └── user
│       └── user.go
├── external
└── usecases
    └── sign_up_usecase.go
```

ソースコードはこちら。

```golang:app/interactors/sign_up_interactor.go
package interactors

import (
        "errors"
        "github.com/sat8bit/golang-clean-architecture/usecases"
)

type SignUpInteractor struct {
}

func NewSignUpInteractor() SignUpInteractor {
        return SignUpInteractor{}
}

func (i SignUpInteractor) SignUp(input usecases.SignUpUseCaseInput) (output *usecases.SignUpUseCaseOutput, err error) {
        return nil, errors.New("not implemented")
}
```

`NewSignUpInteractor()` の返却型は `ISignUpUseCase` であるべきな気もしていて、今絶賛悩み中です。
現状は、`ISignUpUseCase` の実装が `SignUpInteractor` である、みたいな表現は DI 側で表現しています。

話ついでにDIも追加しておきます。

```
.
├── adapters
├── app
│   └── interactors
│       └── sign_up_interactor.go
├── di
│   └── di.go
├── domain
│   └── user
│       └── user.go
├── external
└── usecases
    └── sign_up_usecase.go
```

ソースコードはこちら。
今の時点では Injection も何もありません。

```golang:di/di.go
package di

import (
        "github.com/sat8bit/golang-clean-architecture/app/interactors"
        "github.com/sat8bit/golang-clean-architecture/usecases"
)

func SignUpUseCase() usecases.ISignUpUseCase {
        return interactors.NewSignUpInteractor()
}
```

## 動作させたい！

UseCase を実行するだけの main を作っておきましょう。
今回はmainを作りますが、実際はUseCaseのテストコードを書けばよいです。

```
.
├── adapters
├── app
│   └── interactors
│       └── sign_up_interactor.go
├── di
│   └── di.go
├── domain
│   └── user
│       └── user.go
├── external
├── main
│   └── main.go
└── usecases
    └── sign_up_usecase.go
```

ソースコードはこちら。
既存登録チェックがあるので、２回サインアップしてみるコードにします。

```golang:main/main.go
package main

import (
        "github.com/sat8bit/golang-clean-architecture/di"
        "github.com/sat8bit/golang-clean-architecture/usecases"
        "log"
)

func main() {
        u := di.SignUpUseCase()
        input := usecases.SignUpUseCaseInput{Email: "example@mail.address.com", Password: "abcd1234"}

        if _, err := u.SignUp(input); err != nil {
                log.Printf("Error is %s", err)
        } else {
                log.Print("SignUp succeeded.")
        }

        if _, err := u.SignUp(input); err != nil {
                log.Printf("Error is %s", err)
        } else {
                log.Print("SignUp succeeded.")
        }
}
```

実行してみます。

```bash
sat8bit $ go run main/main.go 
2019/09/13 21:58:15 Error is not implemented
2019/09/13 21:58:15 Error is not implemented
```

## SignUpInteractor の実装

実装を予定している２つの処理は、何れも永続化データの操作になるので、UserRepository の準備を進めます。

UserRepository は緑の Gateway に位置するのですが、**依存関係を緑→赤に保つため**に、まずは定義を app の内部で完結させます。

```
.
├── adapters
├── app
│   ├── gateways
│   │   └── user_repository.go
│   └── interactors
│       └── sign_up_interactor.go
├── di
│   └── di.go
├── domain
│   └── user
│       └── user.go
├── external
├── main
│   └── main.go
└── usecases
    └── sign_up_usecase.go
```

ソースコードはこちら。
気をつけたいのは、**UseCaseから見たときにどう使いたいか**を定義します。

```golang:app/gateways/user_repository.go
package gateways

import (
        "github.com/sat8bit/golang-clean-architecture/domain/user"
)

type IUserRepository interface {
        // Email からユーザを検索したい
        FindByEmail(email string) (user *user.User, err error)
        // User を保存したい
        Save(user user.User) error
}
```

Interface が定義できたので、 SignUpInteractor に依存と実装を追加します。

ソースコードはこちら。

```golang:app/interactors/sign_up_interactor.go
package interactors

import (
        "errors"
        "github.com/sat8bit/golang-clean-architecture/app/gateways"
        "github.com/sat8bit/golang-clean-architecture/domain/user"
        "github.com/sat8bit/golang-clean-architecture/usecases"
)

type SignUpInteractor struct {
        userRepository gateways.IUserRepository
}

func NewSignUpInteractor(
        userRepository gateways.IUserRepository,
) SignUpInteractor {
        return SignUpInteractor{
                userRepository: userRepository,
        }
}

func (i SignUpInteractor) SignUp(input usecases.SignUpUseCaseInput) (output *usecases.SignUpUseCaseOutput, err error) {
        if user, _ := i.userRepository.FindByEmail(input.Email); user != nil {
                return nil, errors.New("email already exists")
        }

        user := user.User{
                Email:    input.Email,
                Password: input.Password,
        }

        if err := i.userRepository.Save(user); err != nil {
                return nil, err
        }

        return &usecases.SignUpUseCaseOutput{}, nil
}
```

これで、赤の円まで完成です。
`SignUpInteractor`が、 **app 配下と domain 配下にしか依存してないこと**がポイントです。
これによって、赤の円より外側、すなわちインターフェースやフレームワークの変更からビジネスロジックを守ることができます。

ちなみに、この時点だとDIが解決できないので動かせません。
次のリポジトリの実装まで我慢。

## UserRepository の実装

今回はサンプルなので、変数にオンメモリで保持する UserRepository を実装します。
Gateway は緑の円になるので、 adapters 配下に用意します。

```
.
├── adapters
│   └── gateways
│       └── user_repository.go
├── app
│   ├── gateways
│   │   └── user_repository.go
│   └── interactors
│       └── sign_up_interactor.go
├── di
│   └── di.go
├── domain
│   └── user
│       └── user.go
├── external
├── main
│   └── main.go
└── usecases
    └── sign_up_usecase.go
```

ソースコードはこちら。
あくまで動作させるためのサンプルなので、**スレッドセーフでない**とか、**毎回Newしたらstore共有されない**とかそういうのは一旦置いておいていただけると・・・

```golang:adapters/gateways/user_repository.go
package gateways

import (
        "errors"
        "github.com/sat8bit/golang-clean-architecture/domain/user"
)

type UserRepository struct {
        store map[string]user.User
}

func NewUserRepository() UserRepository {
        return UserRepository{
                store: map[string]user.User{},
        }
}

func (r UserRepository) FindByEmail(email string) (user *user.User, err error) {
        if u, ok := r.store[email]; ok {
                return &u, nil
        }
        return nil, errors.New("user not already exists")
}

func (r UserRepository) Save(user user.User) error {
        if _, ok := r.store[user.Email]; ok {
                return errors.New("email already exists.")
        }

        r.store[user.Email] = user
        return nil
}
```

続いてDIも修正します。
パッケージ名が gateways で被るので、Interface の方に別名をつけています。

```golang:di/di.go
package di

import (
        "github.com/sat8bit/golang-clean-architecture/adapters/gateways"
        gwif "github.com/sat8bit/golang-clean-architecture/app/gateways"
        "github.com/sat8bit/golang-clean-architecture/app/interactors"
        "github.com/sat8bit/golang-clean-architecture/usecases"
)

func SignUpUseCase() usecases.ISignUpUseCase {
        return interactors.NewSignUpInteractor(
                UserRepository(),
        )
}

func UserRepository() gwif.IUserRepository {
        return gateways.NewUserRepository()
}
```

起動してみましょう。

```
sat8bit $ go run main/main.go 
2019/09/13 22:01:35 SignUp succeeded.
2019/09/13 22:01:35 Error is email already exists
```

うまく動いたようですね。

## WebAPIにする

最後に、httpで叩けるように Controller を作成します。
Controller も緑の円になるので、 adapters 配下に用意します。

```
.
├── adapters
│   ├── controllers
│   │   └── sign_up_controller.go
│   └── gateways
│       └── user_repository.go
├── app
│   ├── gateways
│   │   └── user_repository.go
│   └── interactors
│       └── sign_up_interactor.go
├── di
│   └── di.go
├── domain
│   └── user
│       └── user.go
├── external
├── main
│   └── main.go
└── usecases
    └── sign_up_usecase.go
```

ソースコードはこちら。
ここも最低限の動作をさせる参考程度です。

```golang:adapters/controllers/sign_up_controller.go
package controllers

import (
        "encoding/json"
        "fmt"
        "github.com/sat8bit/golang-clean-architecture/usecases"
        "io"
        "net/http"
)

func NewSignUpController(signUpUseCase usecases.ISignUpUseCase) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
                if r.Method != http.MethodPost {
                        w.WriteHeader(http.StatusMethodNotAllowed)
                        return
                }

                body := make([]byte, r.ContentLength)
                length, err := r.Body.Read(body)
                if err != nil && err != io.EOF {
                        w.WriteHeader(http.StatusBadRequest)
                        return
                }

                var j map[string]string
                err = json.Unmarshal(body[:length], &j)
                if err != nil {
                        w.WriteHeader(http.StatusBadRequest)
                        return
                }

                _, err = signUpUseCase.SignUp(usecases.SignUpUseCaseInput{
                        Email:    j["email"],
                        Password: j["password"],
                })

                if err != nil {
                        w.WriteHeader(http.StatusInternalServerError)
                        w.Write([]byte(fmt.Sprintf("%s", err)))
                        return
                }

                w.WriteHeader(http.StatusOK)
        }
}
```

DI も書く。

```golang:di/di.go
package di

import (
        "github.com/sat8bit/golang-clean-architecture/adapters/controllers"
        "github.com/sat8bit/golang-clean-architecture/adapters/gateways"
        gwif "github.com/sat8bit/golang-clean-architecture/app/gateways"
        "github.com/sat8bit/golang-clean-architecture/app/interactors"
        "github.com/sat8bit/golang-clean-architecture/usecases"
        "net/http"
)

func SignUpController() http.HandlerFunc {
        return controllers.NewSignUpController(
                SignUpUseCase(),
        )
}

func SignUpUseCase() usecases.ISignUpUseCase {
        return interactors.NewSignUpInteractor(
                UserRepository(),
        )
}

func UserRepository() gwif.IUserRepository {
        return gateways.NewUserRepository()
}
```

main も差し替え。

```golang:main/main.go
package main

import (
        "github.com/sat8bit/golang-clean-architecture/di"
        "net/http"
)

func main() {
        c := di.SignUpController()
        http.Handle("/signUp", c)
        http.ListenAndServe(":8080", nil)
}
```

# まとめ

以上の実装で、以下のように整理できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56770/1d94b277-f7f1-3b28-2149-f6ecce5f8929.png)