如何正确传递数据库引用以进行事务处理

时间:2018-08-25 06:44:55

标签: go go-gorm

通常,我将我的Web项目划分为多个域,每个域都有一个服务(业务层)和存储库(数据访问层)。我正在创建一个有两个域(职位和标头)的项目。

在创建或更新作业时,最终,标题也会被更新。该流程由作业/服务(内部称为标头/服务)协调。当发生多次插入/更新时,将使用一个事务来控制该过程。

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

我发现的唯一选择是将db作为存储库方法的参数传递。

pkg / storage / 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
}

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

pkg / jobs / interface.go: 所有方法都接收一个st.DB接口。在函数内部进行类型断言以获取存储空间。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
}

this article中建议的另一种选择是在其中带有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

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
}

1 个答案:

答案 0 :(得分: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
}

对标题执行相同的操作。

Gorm存储库可以实现用户和标头接口。 在测试中,模拟仅实现其中之一。