Goで書くClean Architecture API



# はじめに

クリーンアーキテクチャの考え方を採用したコードをGoで書いてみました。
すでにいろんな方がソースを書かれておりますが、一例として見ていただければと思います。

ソース(メモとタグを新規登録、閲覧できるAPI)
https://github.com/muroon/memo_sample

#### クリーンアーキテクチャの構成図とソースの一覧
![overview3.png](https://qiita-image-store.s3.amazonaws.com/0/76076/44b24c24-a911-ad17-63ae-99dc3cb47096.png)


左側はクリーンアーキテクチャの構成図です。
右側は各ソースがクリーンアーキテクチャのどのレイヤーに属しているかを示しています。


# Clean Architectureとは

Clean Architecture (クリーンアーキテクチャ) とは、 Robert C. Martin (Uncle Bob) 氏が提唱したアーキテクチャである。

https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

こちらの書籍を読んでいる方が多いのではないでしょうか
[Clean Architecture 達人に学ぶソフトウェアの構造と設計](https://www.amazon.co.jp/dp/4048930656/?coliid=I2D3RH8TD0X446&colid=35FO4O7SC5RID&psc=0&ref_=lv_ov_lig_dp_it) 

## 導入のメリット

- テストが書きやすい
Mockを生成しやすくなることによりテストが実施しやすくなる

- フレームワークに依存しない
技術環境が変化、進化に対応しやすい
あまりないケースかもしれないが、
Webアーキテクチャとコンソールアーキテクチャを同じ[ドメイン(ドメイン駆動設計で言うところのドメイン)](https://ja.wikipedia.org/wiki/%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E9%A7%86%E5%8B%95%E8%A8%AD%E8%A8%88)で作成

- データリソースからの独立
フレームワークに依存しないのと同様、技術環境が変化に対応しやすい
例えば、postgreSQLからMySQL、またはMongo, KVSに変更など


## ルール

### 4つのレイヤー

アプリケーションの各機能を下記の4つのレイヤーに分類します。

|レイヤー |内容 |
|---|---|
|Enterprise Business Rules |エンティティーモデル |
|Application Business Rules |ビジネスロジックを担う |
|Interface Adapter |Controller, Presenter |
|Frameworks & Drivers |Frwamework, Database, View |

##### Enterprise Business Rules 
ビジネスルールの為のデータ構造を持ったオブジェクト。
データの実態を表す場所。

##### Application Business Rules
ビジネスルールを操作する場所。
つまりこのアプリケーションで何ができるかを実践します。

##### Interface Adapter
外部からの入力、データの永続化、表示を担当する場所

##### Frameworks & Drivers
Webフレームワーク、DB操作の実際に担うソース、
フロントエンドのUIなどがここに所属しています。

### 外側のレイヤーの要素を直接参照してはならない

<img width="283" alt="izon.png" src="https://qiita-image-store.s3.amazonaws.com/0/76076/c5335939-3192-71ea-e1f5-fcd394dbbfbc.png">


上記の図におけるこの矢印は依存を表しており、
内側のレイヤーから外側のレイヤーの要素への依存を禁じます。
ここでいう依存とは要素(構造体、変数など)への直接参照をさせないということです。

では外側のレイヤー要素を参照せざる得ないは、どうするのでしょうか？
例えば、ビジネスロジックからデータアクセスオブジェクト(敷いてはリーソースへ管理する箇所)

そのようなときは必ずインターフェイスを使用します。

詳しくは[依存性逆転の原則](https://qiita.com/yoshinori_hisakawa/items/25576a62123607a696f6)



# ソースについて

メモとタグを新規登録、閲覧できるRestAPIアプリです。

- メモは関連するタグを付けることが可能
- タグとメモはN対N

## Repository

### インターフェイス

<img width="304" alt="repository_interface.png" src="https://qiita-image-store.s3.amazonaws.com/0/76076/c974d608-9c01-f521-9079-7599f6720d99.png">

- アプリの使用要件からタグとメモそれぞれにrepositoryを設けます
 - tag_repository
 - memo_repository
- トランザクションを担うtransaction_repositoryを設けます

### 実装部

Repositoryの実装はadapter配下に設けてあります。

<img width="318" alt="repository_adapter.png" src="https://qiita-image-store.s3.amazonaws.com/0/76076/7bc4c3a2-692f-be48-0e14-9abea767b016.png">


今回下記の2種類用意してあります。

- db
- memory (ソースのみ、Mock用)

一つのリポジトリに対して、dbとmemoryに分けたのは
例えば、usecaseの単体テストのときに一時的にdbではなく、Mockを使用したりすることが可能だからです。
規模が大きくなるとdbとmemoryのそれぞれ用意するほうがコスト高になるかもしれませんが
あるリポジトリだけMockにしたいみたいなケースもあるかと思います。

#### トランザクションなしの場合

```go
// memo save
_, err = memoRepo.Save(ctx, "Memo Text")
if err != nil {
	panic(err)
}

// tag save
_, err = tagRepo.Save(ctx, "Tag Text")
if err != nil {
	panic(err)
}

```

#### トランザクションありの場合

```go
defer func() {
	if err := recover(); err != nil {
		transactionRepo.Rollback(ctx)
	}
}()

// begin
ctx, err := transactionRepo.Begin(ctx)
if err != nil {
	panic(err)
}

// memo save
_, err = memoRepo.Save(ctx, "Memo Text")
if err != nil {
	panic(err)
}

// tag save
_, err = tagRepo.Save(ctx, "Tag Text")
if err != nil {
	panic(err)
}

// commit
_, err = transactionRepo.Commit(ctx)
if err != nil {
	panic(err)
}
```


つまり、memoリポジトリとtagリポジトリのメソッドはトランザクション有無にかかわらず同一のメソッドが使用できます。
メソッド内部でトランザクションの有無を見ています。


こちらgoroutineにも対応しており、並行処理でも実行可能です。
https://github.com/muroon/memo_sample/blob/master/adapter/db/multi_thread_test.go

## Controller, Presenter

### インターフェイス

![interface.png](https://qiita-image-store.s3.amazonaws.com/0/76076/639de88a-2f7d-5660-01f6-d26a5f6cbaab.png)

presenterのインターフェイスはUseCase層に所属します。


### 実装部

![presenter.png](https://qiita-image-store.s3.amazonaws.com/0/76076/60e56cf6-64c3-dfe9-a46c-5e17850096eb.png)

controller, presenterの実処理はInterface Adapter層に記載してあります。

#### Controller
requestパラメータをInput用モデルへ組み込み、UseCase Interactorに処理を渡しています。

```go
func (c Controller) PostMemo(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

	ctx = addResponseWriter(ctx, w)

	ipt := &input.PostMemo{Text: r.URL.Query().Get("text")}
	c.it.PostMemo(ctx, *ipt) // ←Interactorに処理を渡している
}
```

#### UseCase Interactor

Interactor内から各処理を行い、Presenterへ処理を渡します。

```go
func (i Interactor) PostMemo(ctx context.Context, ipt input.PostMemo) {

	// 新規メモの登録
	id, err := i.memo.Post(ctx, ipt)
	if err != nil {
		i.pre.ViewError(ctx, err) // ←Presenterに処理を渡している
		return
	}

	// 登録メモの取得
	iptf := &input.GetMemo{ID: id}
	memo, err := i.memo.GetMemo(ctx, *iptf)
	if err != nil {
		i.pre.ViewError(ctx, err) // ←Presenterに処理を渡している
		return
	}

	i.pre.ViewMemo(ctx, memo) // ←Presenterに処理を渡している
}
```
#### Presenter

プレゼンターでViewモデルの生成、表示を行います。

```go
func (m presenter) ViewMemo(ctx context.Context, md *model.Memo) {
	defer deleteResponseWriter(ctx)
	w := getResponseWriter(ctx)

	m.JSON(ctx, w, m.render.ConvertMemoJSON(md))
}
```
 
# 最後に
Dependency Injectionを利用して環境変化に対応した仕組みを作る考え方はすごいと思いました。

自分でつくってこんなにも時間がかかるものかと思いました。

ただ、正しいソフトウェアをつくることが最終的には最もはやくソフトウェアをつくることに繋がるとボブおじさんもおっしゃってました。

# 参考

[【ボブおじさんのClean Architectureまとめ】オブジェクト指向 ~SOLIDの原則~](https://qiita.com/yoshinori_hisakawa/items/25576a62123607a696f6)

[実装クリーンアーキテクチャ](https://qiita.com/nrslib/items/a5f902c4defc83bd46b8)

[Clean ArchitectureでAPI Serverを構築してみる](https://qiita.com/hirotakan/items/698c1f5773a3cca6193e)

[やはりお前たちのRepositoryは間違っている](https://qiita.com/mikesorae/items/ff8192fb9cf106262dbf)

[クリーンアーキテクチャの書籍を読んだのでAPIサーバを実装してみた](https://qiita.com/yoshinori_hisakawa/items/f934178d4bd476c8da32)