usecase → domain/{entity, repository, service} ← infra
な依存関係において usecase
をトランザクション境界線にする。domain
で引数としてトランザクションを持つことはしたくないとりあえず, begin, commit, rollback のインターフェース。
domain は, トランザクションするためのインターフェースを持ってる。トランザクションを開始した時, その特定のトランザクションの追跡の目的でコンテキストを伝搬させるため Begin はコンテキストを返すインターフェース。
type Transaction interface {
Begin(ctx context.Context) (context.Context, error)
Commit(ctx context.Context) error
Rollback(ctx context.Context, err error) error
}
usecase で行うのは, 「トランザクションを開始する。必要な時にコミット(ロールバック)する。」という記述で, domain のインターフェースを使ってトランザクションを実装する。
type Usecase struct {
tx repository.Transaction
...
}
...
...
func (uc *Usecase) ...
...
...
ctx, err = uc.tx.Begin(ctx)
if err != nil {
return nil, err
}
// 遅延参照させるため無名関数でラップが必要
defer func() {
if flushErr := uc.flush(ctx, err); flushErr != nil {
err = flushErr
}
}()
// any process ...
}
func (uc *CharactorUsecase) flush(ctx context.Context, err error) {
// panic 発生時も rollback して欲しいので panic recover 処理
// commit もしくは rollback 処理
}
defer
定義時点で, 変数を束縛するとその時の値で束縛されてしまう。
func hoge(err error) {
fmt.Println("hoge: ", err)
}
func foo() (err error) {
defer hoge(err)
err = errors.New("test error")
return
}
func main(){
err := foo()
fmt.Println("main: ", err)
}
hoge: <nil>
main: test error