如何使用不使用 sql.Tx 的现有存储库在 Go 中包装 SQL 事务?

我有一项服务可以一次性创建大量数据。假设我创建user -> user pic -> user contact -> user address


我为每个域创建的每个表。说,在user domain我有一个存储库来创建这样的用户:


stmt, err := db.Prepare(query)

checkError(err)


err = stmt.QueryRowContext(ctx, ...data).Scan(...model)

checkError(err)


return model

在我的案例中,每个存储库都使用Create Statement如上所示的 a,而我不使用db.BeginTx().


这是我谈论的一项服务:


dataUser, errCreate := user.repository.Create(ctx, modelUser)


dataPic, errCreatePic := pic.repository.Create(ctx, modelUserPic)


dataContact, errCreateContact := contact.repository.Create(ctx, modelUserContact)


dataAddress, errCreateAddress := address.repository.Create(ctx, modelUserAddress)

但是如果一个调用失败,在中间,父级将被提交到这里。是否可以在我创建的现有存储库中包装该案例? Transaction


或者我应该在存储库中创建一个方法来处理例如CreateWithTransaction()?


慕沐林林
浏览 193回答 2
2回答

阿波罗的战车

很大程度上取决于您的整个架构是如何设置的。有许多变化和考虑因素需要考虑(我不会详细说明,因为那里有很多文档)。但一般来说,最佳实践是域模型——在许多情况下甚至是服务——完全独立于任何实现选择。它不应该知道 SQL 事务。例如,如果User是 DDD 术语中的聚合根Picture,Contact并且 和Address是值对象,则User负责一致性。在我自己的 DDD 代码中,我使用Ports and Adapters架构和CQRS。我可能UpdateUserDetails在应用程序层中有一个命令,它传递了一个存储库实现(例如,对于 SQL 数据库)并进行事务处理。域层将只定义存储库接口。命令处理程序从存储库中检索聚合根对象User,并对其进行更新(再次以各种方式实现,例如在处理程序本身的单个User.UpdateDetails()或多个步骤中)。只有在没有任何验证或业务规则错误的情况下完成此操作后,处理程序才会持久保存到存储库,在这里我将调用包装到事务中。不过,我的代码(非常具体)对您没有用处。相反,请查看出色的Wild Workouts示例,了解 DDD for Go 的完整介绍,包括事务处理。博客系列逐渐重构应用程序,引入更多与 DDD 相关的概念。它从“DDD lite”版本开始(最佳实践:尽可能简单地开始),然后添加 CQRS,甚至进入事件溯源和微服务(由于“最终一致性”,事务的处理方式将完全不同)。特别是该系列的文章存储库模式:一种简化 Go 服务逻辑的轻松方法,提供了一些关于如何处理事务的有见地的想法。

料青山看我应如是

而不是声明一个存储库,例如type UserRepo struct{   db *sql.DB}func (s UserRepo)Create(ctx context.Context, u modelUser) error{ ... }首选签名,例如type Execer interface {    ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)}type Querier interface {    QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)}type UserRepo struct{}func (s UserRepo)Create(ctx context.Context, db Execer, u modelUser) error{ ... }它添加了两个接口来抽象 DB 的具体底层类型。因此,在调用者中,您可以编写类似于    db := GetDB()    tx := db.Begin()    var err error    defer func() {        if err == nil {            err = tx.Commit()        } else {            err = tx.Rollback() // or use a []error, or else, to not shadow the underlying error.        }    }()    var dataUser something    dataUser, err = user.repository.Create(ctx, tx, modelUser)    if err != nil {        return err    }    var dataPic something    dataPic, err = pic.repository.Create(ctx, tx, modelUserPic)    if err != nil {        return err    }    // etc当您不需要启动事务时,只需传入 db 实例,db:=GetDB()dataUser, err := user.repository.Create(ctx, db, modelUser)if err != nil {return err}dataPic, err := pic.repository.Create(ctx, db, modelUserPic)if err != nil {return err}// etc考虑到如果您需要可重试的操作,您应该包装负责开始/结束事务的整个代码。https://golang.org/pkg/database/sql/#TxAfter a call to Commit or Rollback, all operations on the transaction fail with ErrTxDone.
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go