如何正确传递事务使用的数据库引用

Bru*_*uiz 7 go go-gorm

通常我将 Web 项目分成多个域,每个域都有一个服务(业务层)和存储库(数据访问层)。我正在创建一个项目,其中有两个域(作业和标题)。

创建或更新作业时,标题最终也会更新。该流程由作业/服务编排,作业/服务在内部调用标头/服务。当发生多个插入/更新时,使用事务来控制该过程。

通常,在 Go 中创建事务时,会返回一个“Tx”实例,并应在进一步的查询中使用,直到提交为止。唯一的问题是数据库是在存储库创建时注入的,以后无法更改,因为多个请求将通过引用使用同一存储库。在这种情况下有哪些选择?

我想到的唯一选择是将数据库作为存储库方法的参数传递。

pkg/存储/database.go

package storage

import (
    "chronos/pkg/errors"
    "github.com/jinzhu/gorm"
)

// DB Database storage interface
type DB interface {
    Add(interface{}) error
    Delete(interface{}) error

    Begin() (DB, error)
    Rollback() (DB, error)
    Commit() (DB, error)
}


// Gorm Database implementation using GORM
type Gorm struct {
    *gorm.DB
    SQL *gorm.DB
}

// NewGorm Return new gorm storage instance
func NewGorm(db *gorm.DB) *Gorm {
    db = db.
        Set("gorm:association_autocreate", false).
        Set("gorm:association_autoupdate", false).
        Set("gorm:save_associations", false).
        Set("gorm:association_save_reference", false)

    return &Gorm{db, db}
}

// Begin Begin transaction
func (s *Gorm) Begin() (DB, error) {
    t := s.SQL.Begin()
    if err := t.Error; err != nil {
        return s, errors.Database(err)
    }

    return NewGorm(t), nil
}
Run Code Online (Sandbox Code Playgroud)

cmd/app/main.go

db, err := gorm.Open("mysql", config)
st = storage.NewGorm(db)

rJob := job.NewMySQLRepository(log)
sJob := job.NewService(log, st, rJob)

job.Register(log, router, sJob) // register routes
Run Code Online (Sandbox Code Playgroud)

pkg/jobs/interface.go:所有方法都接收 st.DB 接口。在函数内部完成类型断言以获得 storage.Gorm 实现

type Repository interface {
    GetByUser(db st.DB, userID int) ([]*model.Job, error)
    GetByID(db st.DB, userID int, id int) (*model.Job, error)
    AddHeaders(db st.DB, job *model.Job, headers []*model.Header) (err error)
    UpdateHeaders(db st.DB, job *model.Job, headers []*model.Header) (err error) // extract this to headers repository
}
Run Code Online (Sandbox Code Playgroud)

本文建议的另一个选项是在方法中使用上下文(或者可能是结构?),其中包含 sql DB。但感觉也不对劲。有些人告诉我,Golang 中没有使用存储库和服务模式,但对我来说,将数据库和业务逻辑混合在一起很奇怪。

通过项目传递数据库引用的最佳选择是什么?

编辑1:

在上述解决方案之前它是如何组织的

cmd/app/main.go

db, err := gorm.Open("mysql", config)

rHeader := header.NewMySQLRepository(log, db)
sHeader := header.NewService(log, rJob)

rJob := job.NewMySQLRepository(log, db) // Inject db in the repository
sJob := job.NewService(log, rJob, sHeader) // Inject the repository here and other related services

job.Register(log, router, sJob) // register routes
Run Code Online (Sandbox Code Playgroud)

pkg/jobs/interface.go:

type Repository interface {
    Add(user *model.Job) (error)
    Delete(user *model.Job) (error)
    GetByUser(userID int) ([]*model.Job, error)
    GetByID(userID int, id int) (*model.Job, error)
    AddHeaders(job *model.Job, headers []*model.Header) (err error)
    UpdateHeaders(job *model.Job, headers []*model.Header) (err error) // extract this to headers repository
}
Run Code Online (Sandbox Code Playgroud)

ZAk*_*Aky 1

我不知道 Tx 问题,但我会尝试解决存储库注入问题。

用户逻辑组件应要求调用者组件实现用户存储库接口才能正常运行。

例如,调用者 main() 应该实现存储库并将其注入到用户组件中,当调用者是 Test(t *testing.T) 时,将注入模拟存储库。

无论如何,用户组件不知道存储库接口实现。

我希望所附代码能够解释这个想法。

package main

func main() {
    //Create the repository
    userrep := &DbService{make(map[int]*Job), make(map[int]*Job)}
    //Injecting the repository
    users := Users{userrep}
    job := users.Do(2)
    _ = job

}

//mapdb.go imlement db as map
//Reimplement it for Gorm or anything that apply to the interface.
type DbService struct {
    dbid   map[int]*Job
    dbName map[int]*Job
}
type Job struct {
    name string
}

func (db *DbService) GetByUser(userID int) (*Job, error) {
    return db.dbName[userID], nil
}
func (db *DbService) GetByID(userID int, id int) (*Job, error) {
    return db.dbid[id], nil
}

//users.go

//this interface is part of the users logic.
//The logic component say by that to the caller,
//"If you give me something that implement this interface I will be able to function properly"
type UsersRep interface {
    GetByUser(userID int) (*Job, error) // I think its better to avoid the reference to model.Job here
    GetByID(userID int, id int) (*Job, error)
}

//usersLogic.go
type Users struct {
    UsersRep
}

func (users *Users) Do(id int) *Job {
    //do...
    j, _ := users.GetByUser(id)
    return j
}
Run Code Online (Sandbox Code Playgroud)

对标题执行相同的操作。

Gorm 存储库可以实现 Users 和 Headers 接口。在测试中,模拟将仅实现其中之一。