为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
慕沐林林 2022-06-27 15:45:09
我有一项服务可以一次性创建大量数据。假设我创建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()?
查看完整描述

2 回答

?
阿波罗的战车

TA贡献1862条经验 获得超6个赞

很大程度上取决于您的整个架构是如何设置的。有许多变化和考虑因素需要考虑(我不会详细说明,因为那里有很多文档)。但一般来说,最佳实践是域模型——在许多情况下甚至是服务——完全独立于任何实现选择。它不应该知道 SQL 事务。

例如,如果User是 DDD 术语中的聚合根PictureContact并且 和Address是值对象,则User负责一致性。

在我自己的 DDD 代码中,我使用Ports and Adapters架构和CQRS。我可能UpdateUserDetails在应用程序层中有一个命令,它传递了一个存储库实现(例如,对于 SQL 数据库)并进行事务处理。域层将只定义存储库接口。

命令处理程序从存储库中检索聚合根对象User,并对其进行更新(再次以各种方式实现,例如在处理程序本身的单个User.UpdateDetails()或多个步骤中)。只有在没有任何验证或业务规则错误的情况下完成此操作后,处理程序才会持久保存到存储库,在这里我将调用包装到事务中。

不过,我的代码(非常具体)对您没有用处。

相反,请查看出色的Wild Workouts示例,了解 DDD for Go 的完整介绍,包括事务处理。博客系列逐渐重构应用程序,引入更多与 DDD 相关的概念。它从“DDD lite”版本开始(最佳实践:尽可能简单地开始),然后添加 CQRS,甚至进入事件溯源和微服务(由于“最终一致性”,事务的处理方式将完全不同)。

特别是该系列的文章存储库模式:一种简化 Go 服务逻辑的轻松方法,提供了一些关于如何处理事务的有见地的想法。


查看完整回答
反对 回复 2022-06-27
?
料青山看我应如是

TA贡献1772条经验 获得超8个赞

而不是声明一个存储库,例如


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/#Tx


After a call to Commit or Rollback, all operations on the transaction fail with ErrTxDone.


查看完整回答
反对 回复 2022-06-27
  • 2 回答
  • 0 关注
  • 192 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号