Skip to content

talon-one/go-txhelper

Repository files navigation

go-txhelper Actions Status Coverage Status PkgGoDev GoDoc go-report

go-txhelper is a module that abstracts database transaction behaviour. It does so by differentiating between Read and Write operations.

It is designed for the use of mappers where you do not know whether you need a transaction or not.

Example

package main

import (
	"context"
	"errors"
	"fmt"
	"os"

	"github.com/jackc/pgx/v4"
	"github.com/jackc/pgx/v4/pgxpool"

	"github.com/talon-one/go-txhelper"
)

func main() {
	ctx := context.Background()

	pool, err := pgxpool.Connect(ctx, os.Getenv("DATABASE_URL"))
	if err != nil {
		panic(err)
	}
	defer pool.Close()

	// create a users table
	_, err = pool.Exec(ctx, `CREATE TABLE IF NOT EXISTS users (
		id INTEGER PRIMARY KEY,
		name TEXT NOT NULL
	)`)
	if err != nil {
		panic(err)
	}

	txh, err := txhelper.New(pool, nil)
	if err != nil {
		panic(err)
	}
	// we need to close the helper at the end of our business logic
	defer func() {
		if err := txh.Commit(ctx); err != nil {
			panic(err)
		}
	}()

	um := NewUserMapper(txh)

	id := 1
	wantedName := "Joe"

	// Get the UserName of the specified user
	currentName, err := um.GetUserName(ctx, id)
	if err != nil {
		if !errors.Is(err, pgx.ErrNoRows) {
			panic(err)
		}
		// if there are no rows for this id create a user
		if err := um.CreateUser(ctx, id, wantedName); err != nil {
			panic(err)
		}
		fmt.Printf("Created User (id=%d, name=%s)\n", id, wantedName)
		return
	}
	if currentName != wantedName {
		if err := um.UpdateUserName(ctx, id, wantedName); err != nil {
			panic(err)
		}
		fmt.Printf("Updated User (id=%d, name=%s)\n", id, wantedName)
		return
	}
	fmt.Printf("nothing to do (id=%d, name=%s)\n", id, currentName)
}

type UserMapper struct {
	txh *txhelper.TxHelper
}

func NewUserMapper(txh *txhelper.TxHelper) *UserMapper {
	return &UserMapper{
		txh: txh,
	}
}

func (um *UserMapper) CreateUser(ctx context.Context, id int, name string) error {
	executor, err := um.txh.GetExecutor(ctx, txhelper.InsertOperation)
	if err != nil {
		return err
	}

	_, err = executor.Exec(ctx, "INSERT INTO users VALUES ($1, $2)", id, name)
	return err
}

func (um *UserMapper) GetUserName(ctx context.Context, id int) (string, error) {
	executor, err := um.txh.GetExecutor(ctx, txhelper.ReadOperation)
	if err != nil {
		return "", err
	}

	row := executor.QueryRow(ctx, "SELECT name FROM users WHERE id = $1", id)
	var name string
	if err := row.Scan(&name); err != nil {
		return "", err
	}

	return name, nil
}

func (um *UserMapper) UpdateUserName(ctx context.Context, id int, newName string) error {
	executor, err := um.txh.GetExecutor(ctx, txhelper.UpdateOperation)
	if err != nil {
		return err
	}

	_, err = executor.Exec(ctx, "UPDATE users SET name = $1 WHERE id = $2", newName, id)
	return err
}