亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

Golang 事務性 API 設計

Golang 事務性 API 設計

Go
蝴蝶刀刀 2023-03-21 15:57:35
我正在嘗試使用 Go 遵循Clean Architecture。該應用程序是一個簡單的圖像管理應用程序。我想知道如何最好地為我的存儲庫層設計接口。我不想將所有存儲庫方法組合到一個大接口中,就像我發現的一些示例那樣,我認為在 Go 中通常首選小接口。我不認為有關管理圖像的用例代碼需要知道存儲庫還存儲用戶。所以我想有UserReader,UserWriter和ImageReader。ImageWriter復雜的是代碼需要是事務性的。事務管理屬于 Clean Architecture 存在一些爭論,但我認為用例層需要能夠控制事務。我認為,屬于單個事務的是業務規則,而不是技術細節?,F在的問題是,如何構造接口?功能方法所以在這種方法中,我打開一個事務,運行提供的函數并在沒有錯誤的情況下提交。type UserRepository interface {    func ReadTransaction(txFn func (UserReader) error) error    func WriteTransaction(txFn func (UserWriter) error) error}type ImageRepository interface {    func ReadTransaction(txFn func (ImageReader) error) error    func WriteTransaction(txFn func (ImageWriter) error) error}問題:不,我不能在單個事務中輕松地編寫用戶和圖像,我必須UserImageRepository為此創建一個額外的接口并提供一個單獨的實現。事務作為存儲庫type ImageRepository interface {    func Writer() ImageReadWriter    func Reader() ImageReader}我認為這與功能方法非常相似。它不會解決聯合使用多個存儲庫的問題,但至少可以通過編寫一個簡單的包裝器來實現。一個實現可能是這樣的:type BoltDBRepository struct {}type BoltDBTransaction struct { *bolt.Tx }func (tx *BoltDBTransaction) WriteImage(i usecase.Image) errorfunc (tx *BoltDBTransaction) WriteUser(i usecase.User) error....不幸的是,如果我實現這樣的交易方法:func (r *BoltDBRepository) Writer() *BoltDBTransactionfunc (r *BoltDBRepository) Reader() *BoltDBTransaction因為這沒有實現ImageRepository接口,所以我需要一個簡單的包裝器type ImageRepository struct { *BoltDBRepository }func (ir *ImageRepository) Writer() usecase.ImageReadWriterfunc (ir *ImageRepository) Reader() usecase.ImageReader作為價值的交易type ImageReader interface {    func WriteImage(tx Transaction, i Image) error}type Transaction interface {     func Commit() error}type Repository interface {    func BeginTransaction() (Transaction, error)}存儲庫實現看起來像這樣type BoltDBRepository struct {}type BoltDBTransaction struct { *bolt.Tx }// implement ImageWriterfunc (repo *BoltDBRepository) WriteImage(tx usecase.Transaction, img usecase.Image) error {  boltTx := tx.(*BoltDBTransaction)  ...}問題:雖然這可行,但我必須在每個存儲庫方法的開頭鍵入斷言,這看起來有點乏味。所以這些是我可以想出的方法。哪個最合適,或者有更好的解決方案?
查看完整描述

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、存儲庫實現


查看完整回答
反對 回復 2023-03-21
?
哆啦的時光機

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進行依賴注入。


查看完整回答
反對 回復 2023-03-21
?
幕布斯6054654

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()

}



查看完整回答
反對 回復 2023-03-21
  • 3 回答
  • 0 關注
  • 146 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號