やりたいこと

トランザクション

とりあえず, begin, commit, rollback のインターフェース。

実装

domain/repository

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

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