通常我将 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)
我不知道 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 接口。在测试中,模拟将仅实现其中之一。