douqu8828 2018-08-25 06:44
浏览 62

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

Usually I split my web projects in domains and each domain has a service (business layers) and repository (data access layer). I am creating a project where I have two domains (Jobs and Headers).

When creating or updating an job, eventually, headers will be updated as well. The process is orchestrated by the job/service, which internally calls header/service. As multiple insert/updates happen, a transaction is used to control the process.

Usually, when creating a transaction in Go, a "Tx" instance is returned and should be used in the further queries, until committed. The only problem is that the DB is injected on the repository creation and it cannot be changed later on as multiple requests will be using the same repository through reference. What are the options in cases like this?

The only option I figured out is to pass the db as a parameter of repositories methods.

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: All methods receive a st.DB interface. Inside the function a type assert is done to get the storage.Gorm implementation

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
}

Another option suggested in this article is to use a context (or maybe a struct?) down the methods, with the sql DB in it. But it doesn't feel right either. Some people told me that the repository and service patterns are not used in Golang, but for me is weird to have database and business logic mixed together.

What is the best option to pass the database reference through the project?

EDIT 1:

How it was organized before the above solution

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条回答 默认 最新

  • double0201 2018-08-25 08:11
    关注

    I don't know about the Tx problem but I'll try to address the repository injection issue.

    The users logic component should ask the caller component to implement the users repository interface in order to function properly.

    The calle, main() for example should implement the repository and inject it to the users component, when the caller is Test(t *testing.T) a mock repository will be injected.

    Anyway the users component is not aware of the repository interface implementation.

    I hope the attached code explain the idea.

    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
    }
    

    Do the same for headers.

    The Gorm repository can implement Users and Headers interfaces. In tests the mock will implement only one of them.

    评论

报告相同问题?

悬赏问题

  • ¥15 安卓adb backup备份应用数据失败
  • ¥15 eclipse运行项目时遇到的问题
  • ¥15 关于#c##的问题:最近需要用CAT工具Trados进行一些开发
  • ¥15 南大pa1 小游戏没有界面,并且报了如下错误,尝试过换显卡驱动,但是好像不行
  • ¥15 没有证书,nginx怎么反向代理到只能接受https的公网网站
  • ¥50 成都蓉城足球俱乐部小程序抢票
  • ¥15 yolov7训练自己的数据集
  • ¥15 esp8266与51单片机连接问题(标签-单片机|关键词-串口)(相关搜索:51单片机|单片机|测试代码)
  • ¥15 电力市场出清matlab yalmip kkt 双层优化问题
  • ¥30 ros小车路径规划实现不了,如何解决?(操作系统-ubuntu)