由于这种模式,在同一结构中使用事务和简单数据库连接我该怎么办?
Using transaction and simple DB connection in the same struct because of this pattern what can I do?
我发现 a great example of transactions between Repositories 使用清洁架构方法。
这家伙正在使用 Gorm。
Gorm 具有相同的数据库连接和事务类型,示例:
var db *gorm.DB
var tx *gorm.DB
我是 go-pg
的粉丝。但是这里的类型不同(也许它更好),例如:
var db *pg.DB
var tx *pg.Tx
当然错误是:Cannot use 'tx' (type *Tx) as type *pg.DB
小复制:
package main
import (
"github.com/go-pg/pg/v10"
)
type Player struct {
ID int
Name string
}
type PlayerRepo struct {
db *pg.DB
teamRepo *TeamRepo
}
type TeamRepo struct {
db *pg.DB
}
func NewPlayerRepo(db *pg.DB) *PlayerRepo {
return &PlayerRepo{
db: db,
teamRepo: NewTeamRepo(db),
}
}
func NewTeamRepo(db *pg.DB) *TeamRepo {
return &TeamRepo{db: db}
}
func (r *PlayerRepo) Find(id int) (*Player, error) {
var player Player
err := r.db.Model(&player).Where("id = ?", id).Select()
if err != nil {
return nil, err
}
return &player, nil
}
func (r *PlayerRepo) All() ([]*Player, error) {
// Long code
return nil, nil
}
func (r *PlayerRepo) Insert() (*Player, error) {
// Long code
return nil, nil
}
func (r *PlayerRepo) Update() (*Player, error) {
// Long code
return nil, nil
}
func (r *PlayerRepo) Delete() (*Player, error) {
// Long code
return nil, nil
}
func (r *PlayerRepo) WithTransaction(txFunc func(*PlayerRepo) error) (err error) {
tx, _ := r.db.Begin()
manager := NewPlayerRepo(tx) // <<<--- here the problem! tx is not good here, it's `pg.Tx` not `pg.DB`
err = txFunc(manager)
return
}
我该怎么做才能解决这个问题?
提前致谢。 ❤️
您可以定义一个已经由两者隐式实现的接口:
type DB interface {
Begin() (*Tx, error)
Close() error
Context() context.Context
CopyFrom(r io.Reader, query interface{}, params ...interface{}) (res Result, err error)
CopyTo(w io.Writer, query interface{}, params ...interface{}) (res Result, err error)
Exec(query interface{}, params ...interface{}) (Result, error)
ExecContext(c context.Context, query interface{}, params ...interface{}) (Result, error)
ExecOne(query interface{}, params ...interface{}) (Result, error)
ExecOneContext(c context.Context, query interface{}, params ...interface{}) (Result, error)
Formatter() orm.QueryFormatter
Model(model ...interface{}) *orm.Query
ModelContext(c context.Context, model ...interface{}) *orm.Query
Prepare(q string) (*Stmt, error)
Query(model interface{}, query interface{}, params ...interface{}) (Result, error)
QueryContext(c context.Context, model interface{}, query interface{}, params ...interface{}) (Result, error)
QueryOne(model interface{}, query interface{}, params ...interface{}) (Result, error)
QueryOneContext(c context.Context, model interface{}, query interface{}, params ...interface{}) (Result, error)
RunInTransaction(ctx context.Context, fn func(*Tx) error) error
}
注意:我只知道方法名称匹配,我没有费心检查签名是否也匹配,如果不匹配,您需要相应地编辑界面。
您可以添加一个简单的“编译器检查”:
var _ DB = (*pg.DB)(nil)
var _ DB = (*pg.Tx)(nil)
然后您可以将 PlayerRepo.db
字段的类型从 *pg.DB
更改为新的 DB
界面。
type PlayerRepo struct {
db DB
teamRepo *TeamRepo
}
type TeamRepo struct {
db DB
}
func NewPlayerRepo(db DB) *PlayerRepo {
return &PlayerRepo{
db: db,
teamRepo: NewTeamRepo(db),
}
}
func NewTeamRepo(db DB) *TeamRepo {
return &TeamRepo{db: db}
}
func (r *PlayerRepo) WithTransaction(txFunc func(*PlayerRepo) error) (err error) {
tx, err := r.db.Begin()
if err != nil {
return err
}
defer func() {
// rollback if err; commit if no err
}()
manager := NewPlayerRepo(tx)
err = txFunc(manager)
return
}
如果您的回购类型需要能够调用某些 不 对 pg.DB
和 pg.Tx
通用的方法,因此不由新的 DB
接口定义,那么,一种方法是保留原始类型以供此类使用,例如:
type PlayerRepo struct {
db DB
pg *pg.DB
teamRepo *TeamRepo
}
type TeamRepo struct {
db DB
pg *pg.DB
}
func NewPlayerRepo(db DB, pg *pg.DB) *PlayerRepo {
return &PlayerRepo{
db: db,
pg: pg,
teamRepo: NewTeamRepo(db, pg),
}
}
func NewTeamRepo(db DB, pg *pg.DB) *TeamRepo {
return &TeamRepo{db: db, pg: pg}
}
func (r *PlayerRepo) WithTransaction(txFunc func(*PlayerRepo) error) (err error) {
tx, err := r.db.Begin()
if err != nil {
return err
}
defer func() {
// rollback if err; commit if no err
}()
manager := NewPlayerRepo(tx, r.pg)
err = txFunc(manager)
return
}
请注意,如果您决定使用 orm.DB
,这是合理的,但它缺少一些您需要的方法,并且 pg.DB
和 pg.Tx
都已经实现了这些方法],那么您可以将 orm.DB
嵌入到您的自定义界面中,并仅添加那些缺少的方法。
type DB interface {
Begin() (*Tx, error)
orm.DB
}
我发现 a great example of transactions between Repositories 使用清洁架构方法。
这家伙正在使用 Gorm。
Gorm 具有相同的数据库连接和事务类型,示例:
var db *gorm.DB
var tx *gorm.DB
我是 go-pg
的粉丝。但是这里的类型不同(也许它更好),例如:
var db *pg.DB
var tx *pg.Tx
当然错误是:Cannot use 'tx' (type *Tx) as type *pg.DB
小复制:
package main
import (
"github.com/go-pg/pg/v10"
)
type Player struct {
ID int
Name string
}
type PlayerRepo struct {
db *pg.DB
teamRepo *TeamRepo
}
type TeamRepo struct {
db *pg.DB
}
func NewPlayerRepo(db *pg.DB) *PlayerRepo {
return &PlayerRepo{
db: db,
teamRepo: NewTeamRepo(db),
}
}
func NewTeamRepo(db *pg.DB) *TeamRepo {
return &TeamRepo{db: db}
}
func (r *PlayerRepo) Find(id int) (*Player, error) {
var player Player
err := r.db.Model(&player).Where("id = ?", id).Select()
if err != nil {
return nil, err
}
return &player, nil
}
func (r *PlayerRepo) All() ([]*Player, error) {
// Long code
return nil, nil
}
func (r *PlayerRepo) Insert() (*Player, error) {
// Long code
return nil, nil
}
func (r *PlayerRepo) Update() (*Player, error) {
// Long code
return nil, nil
}
func (r *PlayerRepo) Delete() (*Player, error) {
// Long code
return nil, nil
}
func (r *PlayerRepo) WithTransaction(txFunc func(*PlayerRepo) error) (err error) {
tx, _ := r.db.Begin()
manager := NewPlayerRepo(tx) // <<<--- here the problem! tx is not good here, it's `pg.Tx` not `pg.DB`
err = txFunc(manager)
return
}
我该怎么做才能解决这个问题?
提前致谢。 ❤️
您可以定义一个已经由两者隐式实现的接口:
type DB interface {
Begin() (*Tx, error)
Close() error
Context() context.Context
CopyFrom(r io.Reader, query interface{}, params ...interface{}) (res Result, err error)
CopyTo(w io.Writer, query interface{}, params ...interface{}) (res Result, err error)
Exec(query interface{}, params ...interface{}) (Result, error)
ExecContext(c context.Context, query interface{}, params ...interface{}) (Result, error)
ExecOne(query interface{}, params ...interface{}) (Result, error)
ExecOneContext(c context.Context, query interface{}, params ...interface{}) (Result, error)
Formatter() orm.QueryFormatter
Model(model ...interface{}) *orm.Query
ModelContext(c context.Context, model ...interface{}) *orm.Query
Prepare(q string) (*Stmt, error)
Query(model interface{}, query interface{}, params ...interface{}) (Result, error)
QueryContext(c context.Context, model interface{}, query interface{}, params ...interface{}) (Result, error)
QueryOne(model interface{}, query interface{}, params ...interface{}) (Result, error)
QueryOneContext(c context.Context, model interface{}, query interface{}, params ...interface{}) (Result, error)
RunInTransaction(ctx context.Context, fn func(*Tx) error) error
}
注意:我只知道方法名称匹配,我没有费心检查签名是否也匹配,如果不匹配,您需要相应地编辑界面。
您可以添加一个简单的“编译器检查”:
var _ DB = (*pg.DB)(nil)
var _ DB = (*pg.Tx)(nil)
然后您可以将 PlayerRepo.db
字段的类型从 *pg.DB
更改为新的 DB
界面。
type PlayerRepo struct {
db DB
teamRepo *TeamRepo
}
type TeamRepo struct {
db DB
}
func NewPlayerRepo(db DB) *PlayerRepo {
return &PlayerRepo{
db: db,
teamRepo: NewTeamRepo(db),
}
}
func NewTeamRepo(db DB) *TeamRepo {
return &TeamRepo{db: db}
}
func (r *PlayerRepo) WithTransaction(txFunc func(*PlayerRepo) error) (err error) {
tx, err := r.db.Begin()
if err != nil {
return err
}
defer func() {
// rollback if err; commit if no err
}()
manager := NewPlayerRepo(tx)
err = txFunc(manager)
return
}
如果您的回购类型需要能够调用某些 不 对 pg.DB
和 pg.Tx
通用的方法,因此不由新的 DB
接口定义,那么,一种方法是保留原始类型以供此类使用,例如:
type PlayerRepo struct {
db DB
pg *pg.DB
teamRepo *TeamRepo
}
type TeamRepo struct {
db DB
pg *pg.DB
}
func NewPlayerRepo(db DB, pg *pg.DB) *PlayerRepo {
return &PlayerRepo{
db: db,
pg: pg,
teamRepo: NewTeamRepo(db, pg),
}
}
func NewTeamRepo(db DB, pg *pg.DB) *TeamRepo {
return &TeamRepo{db: db, pg: pg}
}
func (r *PlayerRepo) WithTransaction(txFunc func(*PlayerRepo) error) (err error) {
tx, err := r.db.Begin()
if err != nil {
return err
}
defer func() {
// rollback if err; commit if no err
}()
manager := NewPlayerRepo(tx, r.pg)
err = txFunc(manager)
return
}
请注意,如果您决定使用 orm.DB
,这是合理的,但它缺少一些您需要的方法,并且 pg.DB
和 pg.Tx
都已经实现了这些方法],那么您可以将 orm.DB
嵌入到您的自定义界面中,并仅添加那些缺少的方法。
type DB interface {
Begin() (*Tx, error)
orm.DB
}