3 回答
TA贡献1883条经验 获得超3个赞
存储库是保存数据的地方的表示,架构元素也是如此。
事务是解决非功能需求(原子操作)的技术细节,因此它必须像架构元素中的内部引用或私有功能一样使用。
在这种情况下,如果您的存储库是这样写的:
type UserRepository interface {
func Keep(UserData) error
func Find(UUID) UserData
}
type ImageRepository interface {
func Keep(ImageData) error
func Find(UUID) ImageData
}
事务性方法是一个实现细节,因此您可以创建一个像内部引用一样使用的 UserRepository 和 ImageRepository 的“实现”。
type UserRepositoryImpl struct {
Tx Transaction
}
func (r UserRepository) func Keep(UserData) error { return r.Tx.On(...)}
func (r UserRepository) func Find(UUID) UserData { return r.Tx.WithResult(...)}
通过这种方式,您也可以将用户和图像保留在单个事务中。
例如,如果客户端引用了 userRepository 和 imageRepository 并且它负责 userData 和 imageData 并且它还希望将这两个数据保存在单个事务中,那么:
//open transaction and set in participants
tx := openTransaction()
ur := NewUserRepository(tx)
ir := NewImageRepository(tx)
//keep user and image datas
err0 := ur.Keep(userData)
err1 := ir.Keep(imageData)
//decision
if err0 != nil || err1 != nil {
tx.Rollback()
return
}
tx.Commit()
这是干净、客观的,并且在洋葱架构、DDD 和 3 层架构(Martin Fowler)中运行良好!
在洋葱架构中:
实体:用户和图像(没有业务规则)
Usecase : 存储库接口(应用规则:保留用户和图像)
控制器: A/N
DB/Api:客户端、tx、存储库实现
TA贡献1779条经验 获得超6个赞
保持存储库原样,不要尝试在那里解决事务性 API 的想法。您需要一个单独的存储库注册表来控制您的存储库将如何初始化以及它们的行为方式;原子操作等这是一个很好的例子:
文件:内部/存储库/registry.go
package repository
import (
"context"
"github.com/kataras/app/image"
)
type TransactionFunc = func(Registry) error
type Registry interface {
NewImageRepository() image.Repository
// more repo initialization funcs...
InTransaction(context.Context, func(Registry) error) error
}
文件:内部/存储库/注册表/postgres.go
package registry
import (
"context"
"fmt"
"github.com/kataras/app/image"
"github.com/kataras/app/internal/repository"
"github.com/kataras/pg" // your or 3rd-party database driver package.
)
type PostgresRepositoryRegistry struct {
db *pg.DB
}
var _ repository.Registry = (*PostgresRepositoryRegistry)(nil)
func NewPostgresRepositoryRegistry(db *pg.DB) *PostgresRepositoryRegistry {
return &PostgresRepositoryRegistry{
db: db,
}
}
func (r *PostgresRepositoryRegistry) NewImageRepository() image.Repository {
return image.NewPostgresRepository(r.db)
}
// The important stuff!
func (r *PostgresRepositoryRegistry) InTransaction(ctx context.Context, fn repository.TransactionFunc) (err error) {
if r.db.IsTransaction() {
return fn(r)
}
var tx *pg.DB
tx, err = r.db.BeginDatabase(ctx)
if err != nil {
return
}
defer func() {
if p := recover(); p != nil {
_ = tx.RollbackDatabase(ctx)
panic(p)
} else if err != nil {
rollbackErr := tx.RollbackDatabase(ctx)
if rollbackErr != nil {
err = fmt.Errorf("%w: %s", err, rollbackErr.Error())
}
} else {
err = tx.CommitDatabase(ctx)
}
}()
newRegistry := NewPostgresRepositoryRegistry(tx)
err = fn(newRegistry)
return
}
现在,在您的域服务级别,您只需注入一个repository.Registry,例如PostgresRepositoryRegistry.
文件:内部/服务/image_service.go
package service
import (
"context"
"github.com/kataras/app/internal/repository"
)
type ImageService struct {
registry repository.Registry
}
func NewImageService (registry repository.Registry) *ImageService {
return &ImageService {
registry: registry ,
}
}
func (s *ImageService) DoSomeWork(ctx context.Context, ...) error {
images := s.registry.NewImageRepository()
images.DoSomeWork(ctx, ...)
}
// Important stuff!
func (s *ImageService) DoSomeWorkInTx(ctx context.Context, inputs [T]) error {
return s.registry.InTransaction(ctx, func(r repository.Registry) error) {
images := r.NewImageRepository()
for _, in := range inputs {
if err := images.DoSomeWork(); err!=nil {
return err // rollback.
}
}
return nil
}
}
在您的 API 路由中使用 ImageService。
db, err := pg.Open(...)
// handleError(err)
repoRegistry := registry.NewPostgresRepositoryRegistry(db)
imageService := service.NewImageService(repoRegistry)
// controller := &MyImageController{Service: imageService}
您可以使用Iris进行依赖注入。
TA贡献1876条经验 获得超7个赞
如果你回购必须保留一些状态字段
type UserRepositoryImpl struct {
db Transaction
someState bool
}
func (repo *UserRepositoryImpl) WithTx(tx Transaction) *UserRepositoryImpl {
newRepo := *repo
repo.db = tx
return &newRepo
}
func main() {
repo := &UserRepositoryImpl{
db: connectionInit(),
state: true,
}
repo.DoSomething()
tx := openTransaction()
txrepo := repo.WithTx(tx)
txrepo.DoSomething()
txrepo.DoSomethingElse()
}
- 3 回答
- 0 关注
- 99 浏览
添加回答
举报