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
}