Go Repository Interface

Back

2021-11-10

The problem:I like using interfaces for interacting with the database. It makes things easier to test and I like that it hides the implementation.

One problem that I run into though is having a simple way to use transactions when doing this.

There's probably 10,000 different suggestions, but here's mine.



// can be either a *sqlx.DB or a *sqlx.Tx
// should have methods that are common to both
type executor interface {
	sqlx.ExtContext
	// couldn't find an existing iface with this in it
	SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error
	GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error
}

type Repo interface {
	Begin(ctx context.Context) (Repo, error)
	// has Commit and Rollback sigs
	driver.Tx

	InsertThing(ctx context.Context, thing *Thing) error
	...
}

// Our implementation
type SqlRepo struct {
	DB *sqlx.DB
	// if this is a normal instance it'll be *sql.DB, otherwise a *sqlx.Tx
	executor
}

func (r SqlRepo) Begin(ctx context.Context) (Repo, error) {
	exr, err := r.DB.BeginTxx(ctx, &sql.TxOptions{})

	if err != nil {
		return nil, err
	}
	return &SqlRepo{DB: r.DB, executor: exr}, nil
}

func (r *SqlRepo) Commit() (err error) {
	if e, ok := r.executor.(*sqlx.Tx); ok {
		return e.Commit()
	}
	return errors.New("not in a transaction")
}

func (r *SqlRepo) Rollback() error {
	if e, ok := r.executor.(*sqlx.Tx); ok {
		return e.Rollback()
	}
	return errors.New("not in a transaction")
}

func (r SqlRepo) InsertThing(ctx context.Context, thing *Thing) error {

	res, err := r.executor.QueryContext(ctx, `INSERT INTO things (name) VALUES ($1)
                  RETURNING id, created_at
                  `, thing.Name)
	for res.Next() {
		err = res.Scan(&thing.ID, &thing.CreatedAt)
	}
	return err
}


    


}