Goとクリーンアーキテクチャとトランザクションと

# はじめに

アドベントカレンダー16日目、aptpodのしがないサーバーサイドエンジニアがお届けします。
弊社ではサーバーサイドのプログラミング言語としてGoを利用しています。
Goでアプリケーションを実装していく中で特に頭を悩ませた、Go x クリーンアーキテクチャ x トランザクションについて、学んだこと、どうやって対処したかを書きたいと思います。

# 題材について
解説するにあたり、クリーンアーキテクチャ[^1]を意識したサンプルアプリケーションを作りました。
機能自体はしょうもない、CRD（Update無し!）です。
意識はしていますが、クリーンアーキテクチャの厳密さを言われるとそうでもないです。
しかし、その部分の議論は本題とずれますので特に触れません。

まず全体の絵を。

## ざっくりクラス関連図
![adventcalendar_main_class.png](https://qiita-image-store.s3.amazonaws.com/0/46644/b8dcd0bb-0da4-f775-6059-2d4a3117e24d.png)

## ざっくりシーケンス図
![adventcalendar_main_seq.png](https://qiita-image-store.s3.amazonaws.com/0/46644/caf11562-edf4-e245-375f-5117347dc6c8.png)

ユースケースのアクティベートの部分はトランザクションを意味しています。そこだけ目立たせてみました。

ソースコードは[こちら](https://github.com/miya-masa/go-clean-webapp)。
ブランチ毎にトランザクションのかけ方を変えた実装があります。内容については後述します。


# 困ること

まず、Goのトランザクション制御の実装方法について簡単に説明します。
Goのトランザクション制御の方法として、よく見るコードはDBアクセスオブジェクトを利用してBegin/Commit/Rollbackする方法です。
具体的には次のようなコードです。（sqlxを使っていますが、どれも似たような感じかなと思います。）

```golang
	// dbのオープン！
	db, err := sqlx.Connect("postgres", "user=miya password=miya dbname=miya sslmode=disable")
	if err != nil {
		return err
	}
	tx, err := db.Begin()
	if err != nil {
		return err
	}
	// エラーが起きたらロールバック！成功したらコミット！
	tx.Rollback()
	// or
	tx.Commit()
```

ここでのポイントはトランザクション制御はデータ層[^2]に依存するということです。

一方で、クリーンアーキテクチャを採用したアプリケーションを実装する場合、トランザクション境界は、ユースケース層[^3]におくことが多いです。
クリーンアーキテクチャの依存ルールを守ると、ユースケース層にはデータ層の関心事を持ち込むことができません。

ここで、

* Goのよくあるトランザクション制御の実装はデータ層に依存する。
* ユースケース層はトランザクション制御をかけたいが、データ層に依存できない。

という矛盾が生じます。この矛盾をどうやって解消していくかというのが本記事の主題になります。
バシッと答えはないので、未だ試行錯誤中でもありますが。。。


解説するにあたり、題材として上げたサンプルアプリケーションの `accountInteractor#Store()` という処理にトランザクションを張りたいケースを考えていきます。
この処理内ではデータ層の関心事を持ち込んではいけません。

```
// InputPort I/F。 ControllerはこのI/F経由でユースケースを呼び出す。
type AccountInputPort interface {
	Store(ctx context.Context, in *AccountStoreInput) (*entity.Account, error)
}

func NewAccountInteractor(ar entity.AccountRepository) AccountInputPort {
	return &accountInteractor{
		accountRepository: ar,
	}
}

// UsecaseInteractor
type accountInteractor struct {
	accountRepository    entity.AccountRepository
}

// ユースケースの実処理。ここにトランザクションをかけたい
func (u *accountInteractor) Store(ctx context.Context, in *AccountStoreInput) (*entity.Account, error) {
	return u.accountRepository.Store(ctx, entity.New(in.FirstName, in.FirstName))
}
```

# 解決案０ トランザクションのヘルパー

本当の解決案に進む前にGoのトランザクションのヘルパーと言いますか、ある種トランザクションの実装パターンがあるので簡単に紹介しておきます。

Goではエラーの即時ハンドリングを推奨しています。
そのため、トランザクションを開始してから、エラーが起きたらロールバックをするというコードを書こう[^4]とすると、エラーをハンドリングするたびに `Rollback` をコールするコードになるため、冗長で抜けも発生しがちです。

```golang

// この関数はトランザクション制御をする！
func Process(db *sqlx.DB) error {
	tx, err := db.Beginx()
	if err != nil {
		return err
	}
	if err != nil {
		// 冗長
		tx.Rollback()
		return err
	}
	if _, err := tx.NamedExec(...); err != nil {
		// 冗長
		tx.Rollback()
		return err
	}
	if _, err := tx.NamedExec(...); err != nil {
		// 冗長
		tx.Rollback()
		return err
	}
	if _, err := tx.NamedExec(...); err != nil {
		// 冗長
		tx.Rollback()
		return err
	}
	if _, err := tx.NamedExec(...); err != nil {
		// 冗長....いや！漏れてる！ここ通るとトランザクション終わらないよ！
		return err
	}
	tx.Commit()
	return err
}

```

この問題を解消するために、トランザクションの処理を抽象化したヘルパー関数を用いると便利です。

```golang

// `f` という引数が、任意のトランザクション内のコールバック関数
func DoInTx(db *sqlx.DB, f func(tx *sqlx.Tx) (interface{}, error)) (interface{}, error) {
	tx, err := db.Beginx()
	if err != nil {
		return nil, err
	}
	v, err := f(tx)
	if err != nil {
		tx.Rollback()
		return nil, err
	}
	if err := tx.Commit(); err != nil {
		tx.Rollback()
		return nil, err
	}
	return v, nil
}

```

このような実装があると、トランザクションを利用する側のコードはトランザクションのBegin/Commit/Rollbackを意識することなく実装できます。
エラーが返ると、ロールバックというのも直感的ですね。deferで全部拾う実装のほうがきれいかな。
`tx.Rollback()` とか `tx.Commit()` とかも `error` を吐くので厳密にはエラーハンドリングしきれていませんが、これも主題とはズレるので無視してください。

次にこのヘルパー関数の利用側の実装を見ていきます。Begin/Commit/Rollbackが出てこず、冗長だった `Rollback` も無くなります。

```golang
func Process(db *sqlx.DB) error {
	_, err := DoInTx(db, func(tx *sqlx.Tx) (interface{}, error) {
		if err != nil {
			return nil, err
		}
		if _, err := tx.NamedExec(...); err != nil {
			return nil, err
		}
		if _, err := tx.NamedExec(...); err != nil {
			return nil, err
		}
		if _, err := tx.NamedExec(...); err != nil {
			return nil, err
		}
		if _, err := tx.NamedExec(...); err != nil {
			return nil, err
		}
		return nil,　nil
	})
  return err
}

```

スッキリですね！
さて、ではいよいよ本題です！

# 解決案１ ユースケース層をラップする

[実装](https://github.com/miya-masa/go-clean-webapp/tree/feature/wrap_uc)

`accountInteractor` をラップした、トランザクションだけを管理する薄い `AccountInputPort` の実装を作るというのが、解決案１です。実装を見ていきましょう。

```golang
type txAccountInteractor struct {
	db *sqlx.DB
}

// データベースのオブジェクトをもらって InputPort実装を返却する。
func NewAccountInteractorTx(db *sqlx.DB) AccountInputPort {
	return &txAccountInteractor{db: db}
}

func (u *txAccountInteractor) Store(ctx context.Context, in *AccountStoreInput) (*entity.Account, error) {
	// トランザクションを開始して本当のユースケースの実装作って呼び出すだけのうすーーーい処理。
	v, err := database.DoInTx(u.db, func(tx *sqlx.Tx) (interface{}, error) {
		ar := database.NewAccount(tx)
		return NewAccountInteractor(ar, dr).Store(ctx, in)
	})
	return v.(*entity.Account), err
}
```

トランザクションの制御処理と、ユースケース層のロジックを分離できています。
こうすると、例えば、純粋なユースケースロジックのテストをしたければ元実装の `NewAccoiuntIntractor()` で生成した`AccountInputPort` I/F経由で行えば問題ありません。この時の `Repository` はよしなにmockを利用することができ、データ層に依存せずにロジックのテストが可能です。

アプリケーション実行時には `NewAccountInteractorTx()` で生成した `AccountInputPort` 利用すればOKです。

## メリット
* 分かりやすい
* 既存実装の拡張もしやすい

## デメリット
* 実行時にインスタンスを都度作るのでその生成コストが無駄
    * ユースケースの生成コスト
    * リポジトリの生成コスト
* すべてのユースケースをラップするファクトリが必要なため記述が冗長になる。実装コストも増える。
    * 自動生成とか作れば回避は可能だけど。
* トランザクションありのラップした実装はどこの層だろうか
    * データ層に依存しているためユースケース層ではない。データ層に `InputPort` の実装を持っていくのもおかしい。
    * アプリケーション立ち上げ時のmain関数の責務が妥当かな。

# 解決案２ コンテキストにトランザクションオブジェクトをセットする

[実装](https://github.com/miya-masa/go-clean-webapp/tree/feature/use_context)


この案は少し分かりづらいのでまずクラス関連とシーケンスから。

## クラス関連
![adventcalendar_2_class_diag.png](https://qiita-image-store.s3.amazonaws.com/0/46644/bf113fa1-545e-7e1c-bdb0-fbe2dac6c472.png)

## シーケンス図
![adventcalendar_2_sequence.png](https://qiita-image-store.s3.amazonaws.com/0/46644/e5ccd648-032a-15ac-fe46-8d15b71dc8e5.png)

図と実装と行き来しながらですががんばっていきましょ。

では、実装を追っていきます。
まず、トランザクション処理が必要なユースケースおよび、リポジトリのメソッドシグニチャに、 `context.Context` を指定します。題材に示したすべてのユースケースメソッド、及びリポジトリには`context.Context` が第一引数にあるので、ここのコードは割愛します。

次に、トランザクション管理用のI/Fです。解決案０で示したヘルパ関数をI/Fとして切り出したイメージです。
大きく異なる点は、ヘルパ関数のメソッドシグニチャの第一引数は `*sqlx.Tx` でしたが、 `context.Context` とし、データ層との依存を切り離している部分です。
トランザクション管理用のI/Fは外界への依存度を低くし、どこからでも利用できるようなユーティリティくらいの位置づけにする狙いがあります。
`context.Context` は標準パッケージですが、I/Fなので `*sqlx.Tx` 構造体と比較し依存度は低いと考えて差し支えないでしょう。

```golang
// これはデータ層ではない！シグニチャのどこを見てもデータ層ぽいものはない！
type Transaction interface {
	DoInTx(context.Context, func(context.Context) (interface{}, error)) (interface{}, error)
}
```

次はこのトランザクションI/Fの実装を見ていきます。この実装はデータ層に依存します。
トランザクション用のオブジェクトを `context.Context` にセットし、トランザクション制御をかけたいコールバック用の関数にトランザクションオブジェクトを放り込んだ `ctx` を連携します。

```golang

var txKey = struct{}{}

type tx struct {
	db *sqlx.DB
}

func NewTransaction(db *sqlx.DB) transaction.Transaction {
	return &tx{db: db}
}

func (t *tx) DoInTx(ctx context.Context, f func(ctx context.Context) (interface{}, error)) (interface{}, error) {
	tx, err := t.db.BeginTxx(ctx, &sql.TxOptions{})
	if err != nil {
		return nil, err
	}

	// ここでctxへトランザクションオブジェクトを放り込む。
	ctx = context.WithValue(ctx, &txKey, tx)

	// トランザクションの対象処理へコンテキストを引き継ぎ
	v, err := f(ctx)
	if err != nil {
		tx.Rollback()
		return nil, err
	}

	if err := tx.Commit(); err != nil {
		// エラーならロールバック
		tx.Rollback()
		return nil, err
	}
	return v, nil
}

// context.Contextからトランザクションを取得する関数も忘れずに！
func GetTx(ctx context.Context) (*sqlx.Tx, bool) {
	tx, ok := ctx.Value(&txKey).(*sqlx.Tx)
	return tx, ok
}

```

ユースケース層の `Store()` を、`transaction.Transaction` I/Fを使ってトランザクション制御を行うように修正します。

```golang

// トランザクションI/Fを受け取ってセット
func NewAccountInteractor(ar entity.AccountRepository, tx transaction.Transaction) AccountInputPort {
	return &accountInteractor{
		accountRepository: ar,
		trancaction:       tx,
	}
}

type accountInteractor struct {
	accountRepository entity.AccountRepository
	trancaction       transaction.Transaction
}

func (u *accountInteractor) Store(ctx context.Context, in *AccountStoreInput) (*entity.Account, error) {
	// トランザクションを開始
	v, err := u.trancaction.DoInTx(ctx, func(ctx context.Context) (interface{}, error) {
		// 必ず、連携されたctxを使うこと！
		return u.accountRepository.Store(ctx, &entity.Account{
			UUID:      genUUID(),
			FirstName: in.FirstName,
			LastName:  in.LastName,
		})
	})
	return v.(*entity.Account), err
}

```

最後にデータ層です。
 `context.Context` からトランザクションオブジェクトを取り出してクエリを実行します。余談ですが、ダックタイピングいいですね。

```golang

func (u *accountRepository) Store(ctx context.Context, account *entity.Account) (*entity.Account, error) {

	var dao interface {
		NamedExec(query string, arg interface{}) (sql.Result, error)
	}
	// トランザクションオブジェクトをコンテキストから取得する
	dao, ok := GetTx(ctx)
	if !ok {
		// 見つからなかったときはよしなに。ここではすでに持っているdbオブジェクトを利用している。
		dao = u.db
	}
	if _, err := dao.NamedExec("INSERT INTO account(uuid, department_uuid, first_name, last_name) VALUES(:uuid, :department.uuid, :first_name, :last_name)", account); err != nil {
		return nil, err
	}
	return account, nil
}

```

お疲れ様でした。よく見ていただけると分かりますが、ユースケース層からはデータ層の関心事から分離できています。ちなみに、トランザクション管理はI/F経由なので、Noop実装作っておくと、テストとかに便利です。

```
type Noop struct {
}

func (n *Noop) DoInTx(ctx context.Context, f func(context.Context) (interface{}, error)) (interface{}, error) {
	// 何もしない。
	return f(ctx)
}
```


## メリット
* １と比較し、インスタンスの生成コスト減
* トランザクションの関心事が分離できており冗長コードが減る

## デメリット
* `context.Context.Value()` 乱用感が否めない
    * データアクセスオブジェクトをコンテキストに入れるのはアンチパターン
    * ただ、トランザクションはスコープがあるので許容範囲か。オブジェクトも新規生成だしね。と思っている。
* リポジトリ/ユースケースに `context.Context` が必須
* アプリケーションの見通しが悪くなる
    * データ層で取得したデータアクセスオブジェクトはどこでセットされているんだっけ？的な。

# 解決案その３ トランザクションを集約単位とし、結果整合を許容する

[実装](https://github.com/miya-masa/go-clean-webapp/tree/master)

クリーンアーキテクチャはドメイン駆動設計（DDD）と相性がよく、その２つはセットで語られることが多いように思います[^5]。

DDDには

* 集約という単位がある。
* `Repository` は集約単位である。
* トランザクションは集約の単位で行う。
* １ユースケース１集約の操作となるようにアプリケーションを設計する

という原則（しっくりくる言葉が思いつかず...）があります[^6]。

集約の詳細は省きますが、トランザクションの境界は、`Repository` の実装である、データ層としてしまい、そもそもユースケース層にトランザクション境界を置くのはやめようというのが解決案３です。

```golang

type accountRepository struct {
	db *sqlx.DB
}

func NewAccount(db *sqlx.DB) entity.AccountRepository {
	return &accountRepository{db: db}
}

func (u *accountRepository) Store(ctx context.Context, account *entity.Account) (*entity.Account, error) {
	// リポジトリの実装内でトランザクションの開始と終了をする。
	val, err := DoInTx(u.db, func(tx *sqlx.Tx) (interface{}, error) {
		if _, err := tx.NamedExec("INSERT INTO account(uuid, department_uuid, first_name, last_name) VALUES(:uuid, :department.uuid, :first_name, :last_name)", account); err != nil {
			return nil, err
		}
		return account, nil
	})
	if err != nil {
		return nil, err
	}
	return val.(*entity.Account), nil
}

```


## メリット
* 一番実装がわかりやすい

## デメリット
* そもそもDDDとか集約って何？といったところから、設計の敷居が高い。
  * 高いけど、規模によっては頑張らなくちゃね！
* 結果整合を担保するアプリケーション設計難度が高いケースもあるかも
  * 集約をまたいだ一貫性を担保したいということは結構ある。

# で、結局どれなの？

~~それぞれ、一長一短ですが、個人的には解決３が良いかなと思っています。
トランザクションオブジェクトというのも、RDB固有かなと思いますし、Restとかでサービス間連携をすると結果整合を取らざるを得なくなりますので。
ただ、一貫性がとにかく大事で、RDBしか使いませんとかであれば、ユースケース単位で確実にトランザクションの開始終了を制御する１，２の方が良いと思います。~~

**2019年7月19日追記ここから**
解決３で実装して行ったのですが、トランザクションの境界は集約の操作単位であるものの、リポジトリの一つのメソッドがトランザクションの操作単位ではなく、あくまで集約の操作の単位はユースケースであるということを勘違いして不都合が出てきたので、解決1or2が良さそうです。個人的には2がおすすめです。
**ここまで**

行く末や実装コストなどを考えながら実装も変えていければよいのではないでしょうかね。アプリケーションの規模などが理由で、クリーンアーキテクチャもDDDも必要なの？という話もありますし。

以上です。ありがとうございました！

[^1]: https://blog.tai2.net/the_clean_architecture.html 
[^2]: クリーンアーキテクチャの円ではインターフェースアダプター層ですが、便宜上データ層と書かせていただきます
[^3]: 従来レイヤ型アプリケーションではアプリケーションサービスとも言ったりします
[^4]: 大体そうなるはず
[^5]: 多いというだけで、必ずセットというわけではありません。
[^6]: すごくざっくりなので、不確かだったらすみません。