Go Repository Interface
Back2021-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
}