通常,我将我的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
}
答案 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存储库可以实现用户和标头接口。 在测试中,模拟仅实现其中之一。