Паттерн «With-замыкание»

Чтобы не оборачивать бесконечно SQL вызовы в транзакции можно применить такую красивую конструкцию:

func WithTransaction(ctx context.Context, db *sql.DB, fn func(tx *sql.Tx) error) error {
    tx, err := db.BeginTx(ctx, nil)
    if err != nil {
        return err
    }
    defer tx.Rollback() // откат, если что-то пойдет не так
 
    if err := fn(tx); err != nil {
        return err
    }
 
    return tx.Commit()
}

и потом вызывать в виде:

err := WithTransaction(ctx, db, func(tx *sql.Tx) error {
    _, err := tx.ExecContext(ctx, "INSERT INTO users(name) VALUES($1)", "Ivan")
    if err != nil {
        return err
    }
 
    _, err = tx.ExecContext(ctx, "INSERT INTO accounts(user_id, balance) VALUES($1, $2)", 1, 1000)
    return err
})
 
if err != nil {
    fmt.Println("Ошибка транзакции:", err)
} else {
    fmt.Println("Транзакция успешно завершена")
}