GoのTransactionでラッパー関数


毎回の処理で都度Transactionを書く

こんな感じ↓

func (r *Repository) Do() error
    tx, err := r.db.Begin()
    if err != nil {
        return err
    }
    defer func() {
        // panic
	if p := recover(); p != nil {
	  if err := tx.Rollback(); err != nil {
	     log.Printf("failed to MySQL Rollback: %v", e)
           }
	   // re-throw panic after Rollback
	   panic(p)
	}
        // error
        if err != nil {
           if err := tx.Rollback(); err != nil {
		log.Printf("failed to MySQL Rollback: %v", e)
	    }
	    return 
        }
	// 正常
        if err := tx.Commit(); err != nil {
	  log.Printf("failed to MySQL Commit: %v", e)
	}
    }()
    if _, err = tx.Exec(...); err != nil {
        return err
    }
    return nil
}

コード量も多くなって、毎回同じ処理を書くのであまりイケてない。

ラッパー関数にする

func (t *Transaction) Transaction(ctx context.Context, f func(ctx context.Context, tx *sql.Tx) error) error {
        // trasaction start
	tx, err := t.db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer func() {
		// panic
		if p := recover(); p != nil {
			if err := tx.Rollback(); err != nil {
				log.Printf("failed to MySQL Rollback: %v", e)
			}
			// re-throw panic after Rollback
			panic(p)
		}
		// error
		if err != nil {
			if err := tx.Rollback(); e != nil {
				log.Printf("failed to MySQL Rollback: %v", e)
			}
			return
		}
		// 正常
		if err := tx.Commit();err != nil {
			log.Printf("failed to MySQL Commit: %v", e)
		}

	}()
	
	// 主処理実行
	if err := f(ctx, tx); err != nil {
		return err
	}
	return nil
}

実際に使う時はこんな感じ。
トランザクションは色々な関数で使われるので、「開発する人によってrecover漏れてた」など発生しないメリットがある。
ラッパーにするのは必須になってくる。

func (r *Repository) Do(ctx context.Context) error {
    return r.transaction.Transaction(ctx, func(ctx context.Context, tx *sql.Tx) error {
        if _, err := tx.Exec(...); err != nil {
            return err
        }
        return nil
    })
}