简体   繁体   English

由于这种模式,在同一个结构中使用事务和简单的数据库连接我该怎么办?

[英]Using transaction and simple DB connection in the same struct because of this pattern what can I do?

I found a great example of transactions between Repositories using a Clean Architecture approach.我发现了一个使用 Clean Architecture 方法在 Repositories 之间进行交易的很好的例子

This guy is using Gorm .这家伙正在使用Gorm

Gorm has the same type for a DB connection and a transaction, example: Gorm 对数据库连接和事务具有相同的类型,例如:

var db *gorm.DB
var tx *gorm.DB

I'm a fan of go-pg .我是go-pg的粉丝。 But here the types are different (maybe it's even better as is), example:但是这里的类型是不同的(也许更好),例如:

var db *pg.DB
var tx *pg.Tx

And of course the error is: Cannot use 'tx' (type *Tx) as type *pg.DB当然错误是: Cannot use 'tx' (type *Tx) as type *pg.DB

A small reproduction:一个小复制:

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
}

What can I do to fix this?我能做些什么来解决这个问题?

Thanks in advance.提前致谢。 ❤️ ❤️

You can define an interface that is already, implicitly implemented by both:您可以定义一个已经由两者隐式实现的接口:

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
}

NOTE: I only know that the method names match, I didn't bother checking if the signatures also do, if they don't, you'll need to edit the interface accordingly.注意:我只知道方法名称匹配,我没有费心检查签名是否也匹配,如果不匹配,您需要相应地编辑界面。

You can add a simple "compiler check":您可以添加一个简单的“编译器检查”:

var _ DB = (*pg.DB)(nil)
var _ DB = (*pg.Tx)(nil)

And then you can change the type of the PlayerRepo.db field from *pg.DB to your new DB interface.然后您可以将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
}

If your repo types need to be able to invoke some of the methods that are not common to both pg.DB and pg.Tx , and therefore not defined by the new DB interface, then, one approach would be to retain the original types for such use, for example:如果您的 repo 类型需要能够调用pg.DBpg.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
}

Note that if you decide to use orm.DB , which is reasonable, but it is missing some of the methods that you need and that are already implemented by both pg.DB and pg.Tx , then you could embed orm.DB into your custom interface and add only those methods that are missing.请注意,如果您决定使用orm.DB ,这是合理的,但它缺少您需要的一些方法并且已经由pg.DBpg.Tx ,那么您可以将orm.DB嵌入到您的自定义接口并仅添加那些缺少的方法。

type DB interface {
    Begin() (*Tx, error)
    orm.DB
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 使用和继承适配器模式以实现简单的数据库连接 - Adapter pattern in use and inheritance for a simple DB connection 我使用哪种简单的数据库解决方案将解析的变量从eclipse提取到数据库 - What simple DB-solution do I use to extract my parsed variables from eclipse to a DB 您通常如何处理数据库事务日志? - What do you normally do with DB transaction logs? 如果网站与数据库位于同一服务器上,如何构建数据库连接字符串? - How do I build the database connection string if the website is on the same server as the db? 如何使用spring JPA在同一事务中的不同数据库上维护多个sql查询 - How do I maintain several sql queries on different databases in same transaction using spring JPA 如果python中没有错误,如何保存整个数据库事务 - How can I save the whole DB transaction if there is no error in python 在分布式事务期间保持数据库连接活动 - Keeping DB connection alive during distributed transaction 如果我在同一事务中进行了未提交的更新后读取了结果 - What is the result if I read after a uncommited update within the same transaction 如果交易失败怎么办? - What to do if a transaction fails? 使用声明式事务管理时,如何显式提交Spring db事务? - How can one explicitly commit a spring db transaction when using declarative transaction management?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM