Let we have 2 methods
func createClient(db *sql.DB, ...) error // creates a new client
func createOrder(db *sql.DB, ...) error // creates a new order
Each of these method can be run on some *sql.DB, for example,
var mainDb *sql.DB // initialized somewhere in main() method
func orderHandler(r,w) {
...
err := createOrder(mainDb, ...)
...
}
But what if i want to run both these methods in one transaction. For example,
func importOrdersHandler(r,w) {
...
tx, err:= mainDb.Begin()
...
err = createClient(tx, clientData) // but method defined on *sql.DB, not *sql.Tx ?!
err = createOrder(tx, orderData)
...
My solution:
Define a wrapper around *sql.DB, *sql.Tx:
type Database struct {
db *sql.DB
tx *sql.Tx
}
// Redefine all standart methods from sql package, such as
// Exec(...)
// Query(..)
// QueryRow(...)
// Also method to run commands in one transaction:
func TransactionDo(db *sql.DB, body func(*Database) error) error {
tx, err := db.Begin()
...
d, err := NewDb(nil, tx)
....
err = body(d)
...
return tx.Commit()
}
In this way our ordersImportHandler can be realized like that:
func importOrdersHandler(r,w) {
for row := range parseFile(formFile(r)) {
...
err := TransactionDo(mainDb, func(d *Database) error {
err = createClient(d, clientData)
...
err = createOrder(d, orderData)
// if an error occurs in TransactionDo then transaction wiil be
// rollbacked, else commit it
createClient, createOrder must be rewrited to use *Database object insted of *sql.DB
What do you think about such solution? May be there another better and idiomatic way to do that