Go: use closures to simplify database operation code

created at 07-27-2021 views: 4

In daily engineering, we may have to open affairs to complete some operations, so there will be the following code:

// UpdateTaskRemark Update task remark
func UpdateTaskRemark(taskID uint32, remark string) error {
        sql, args, err := sq.Update("tasks").Set("remark", remark).Set("updated_at", getNowTS()).Where("id = ?", taskID).ToSql()
        if err != nil {
                return err
        }

        tx, err := db.Beginx()
        if err != nil {
                return err
        }

        _, err = tx.Exec(sql, args...)
        if err != nil {
                return err
        }

        err = tx.Commit()
        if err != nil {
                logrus.Errorf("failed to commit transaction: %s, rollback it", err)
                return tx.Rollback()
        }

        return nil
}

If there is only one, it seems that there is no problem, but once there are more operations, it is not very good to write this bunch of repetitive codes in each model operation function. Therefore, we have to think of a way to abstract and encapsulate the operations of opening a transaction, committing, and rolling back. With the help of closures we can do this:

// Utility functions are used to execute in a transaction, automatic commit and rollback
type TxFunc func(tx *sqlx.Tx) error

// ExecInTx passes in a closure function, the signature type is TxFunc. Open the transaction first in ExecInTx,
// Then pass the transaction to the closure function, and commit if the closure function does not return an error, otherwise it will roll back.
func ExecInTx(f TxFunc) error {
        tx, err := db.Beginx()
        if err != nil {
                return err
        }

        err = f(tx)
        if err != nil {
                return err
        }

        err = tx.Commit()
        if err != nil {
                logrus.Errorf("failed to commit transaction: %s, rollback it", err)
                return tx.Rollback()
        }

        return nil
}

As a result, the model operation function is simplified to:

// UpdateTaskRemark Update task remark
func UpdateTaskRemark(taskID uint32, remark string) error {
        sql, args, err := sq.Update("tasks").Set("remark", remark).Set("updated_at", getNowTS()).Where("id = ?", taskID).ToSql()
        if err != nil {
                return err
        }

        return ExecInTx(func(tx *sqlx.Tx) error {
                _, err := tx.Exec(sql, args...)
                return err
        })
}
created at:07-27-2021
edited at: 07-27-2021: