簡體   English   中英

由於這種模式,在同一個結構中使用事務和簡單的數據庫連接我該怎么辦?

[英]Using transaction and simple DB connection in the same struct because of this pattern what can I do?

我發現了一個使用 Clean Architecture 方法在 Repositories 之間進行交易的很好的例子

這家伙正在使用Gorm

Gorm 對數據庫連接和事務具有相同的類型,例如:

var db *gorm.DB
var tx *gorm.DB

我是go-pg的粉絲。 但是這里的類型是不同的(也許更好),例如:

var db *pg.DB
var tx *pg.Tx

當然錯誤是: Cannot use 'tx' (type *Tx) as type *pg.DB

一個小復制:

package main

import (
    "github.com/go-pg/pg/v10"
)

type Player struct {
    ID   int
    Name string
}

type PlayerRepo struct {
    db       *pg.DB
    teamRepo *TeamRepo
}

type TeamRepo struct {
    db *pg.DB
}

func NewPlayerRepo(db *pg.DB) *PlayerRepo {
    return &PlayerRepo{
        db:       db,
        teamRepo: NewTeamRepo(db),
    }
}

func NewTeamRepo(db *pg.DB) *TeamRepo {
    return &TeamRepo{db: db}
}

func (r *PlayerRepo) Find(id int) (*Player, error) {
    var player Player
    err := r.db.Model(&player).Where("id = ?", id).Select()
    if err != nil {
        return nil, err
    }
    return &player, nil
}

func (r *PlayerRepo) All() ([]*Player, error) {
    // Long code
    return nil, nil
}

func (r *PlayerRepo) Insert() (*Player, error) {
    // Long code
    return nil, nil
}

func (r *PlayerRepo) Update() (*Player, error) {
    // Long code
    return nil, nil
}

func (r *PlayerRepo) Delete() (*Player, error) {
    // Long code
    return nil, nil
}

func (r *PlayerRepo) WithTransaction(txFunc func(*PlayerRepo) error) (err error) {
    tx, _ := r.db.Begin()
    manager := NewPlayerRepo(tx) // <<<--- here the problem! tx is not good here, it's `pg.Tx` not `pg.DB`
    err = txFunc(manager)
    return
}

我能做些什么來解決這個問題?

提前致謝。 ❤️

您可以定義一個已經由兩者隱式實現的接口:

type DB interface {
    Begin() (*Tx, error)
    Close() error
    Context() context.Context
    CopyFrom(r io.Reader, query interface{}, params ...interface{}) (res Result, err error)
    CopyTo(w io.Writer, query interface{}, params ...interface{}) (res Result, err error)
    Exec(query interface{}, params ...interface{}) (Result, error)
    ExecContext(c context.Context, query interface{}, params ...interface{}) (Result, error)
    ExecOne(query interface{}, params ...interface{}) (Result, error)
    ExecOneContext(c context.Context, query interface{}, params ...interface{}) (Result, error)
    Formatter() orm.QueryFormatter
    Model(model ...interface{}) *orm.Query
    ModelContext(c context.Context, model ...interface{}) *orm.Query
    Prepare(q string) (*Stmt, error)
    Query(model interface{}, query interface{}, params ...interface{}) (Result, error)
    QueryContext(c context.Context, model interface{}, query interface{}, params ...interface{}) (Result, error)
    QueryOne(model interface{}, query interface{}, params ...interface{}) (Result, error)
    QueryOneContext(c context.Context, model interface{}, query interface{}, params ...interface{}) (Result, error)
    RunInTransaction(ctx context.Context, fn func(*Tx) error) error
}

注意:我只知道方法名稱匹配,我沒有費心檢查簽名是否也匹配,如果不匹配,您需要相應地編輯界面。

您可以添加一個簡單的“編譯器檢查”:

var _ DB = (*pg.DB)(nil)
var _ DB = (*pg.Tx)(nil)

然后您可以將PlayerRepo.db字段的類型從*pg.DB為新的DB接口。

type PlayerRepo struct {
    db       DB
    teamRepo *TeamRepo
}

type TeamRepo struct {
    db DB
}

func NewPlayerRepo(db DB) *PlayerRepo {
    return &PlayerRepo{
        db:       db,
        teamRepo: NewTeamRepo(db),
    }
}

func NewTeamRepo(db DB) *TeamRepo {
    return &TeamRepo{db: db}
}


func (r *PlayerRepo) WithTransaction(txFunc func(*PlayerRepo) error) (err error) {
    tx, err := r.db.Begin()
    if err != nil {
        return err
    }
    defer func() {
        // rollback if err; commit if no err
    }()
    manager := NewPlayerRepo(tx)
    err = txFunc(manager)
    return
}

如果您的 repo 類型需要能夠調用pg.DBpg.Tx通用的一些方法,因此沒有由新的DB接口定義,那么,一種方法是保留原始類型這樣的使用,例如:

type PlayerRepo struct {
    db       DB
    pg       *pg.DB
    teamRepo *TeamRepo
}

type TeamRepo struct {
    db DB
    pg *pg.DB
}

func NewPlayerRepo(db DB, pg *pg.DB) *PlayerRepo {
    return &PlayerRepo{
        db:       db,
        pg:       pg,
        teamRepo: NewTeamRepo(db, pg),
    }
}

func NewTeamRepo(db DB, pg *pg.DB) *TeamRepo {
    return &TeamRepo{db: db, pg: pg}
}

func (r *PlayerRepo) WithTransaction(txFunc func(*PlayerRepo) error) (err error) {
    tx, err := r.db.Begin()
    if err != nil {
        return err
    }
    defer func() {
        // rollback if err; commit if no err
    }()
    manager := NewPlayerRepo(tx, r.pg)
    err = txFunc(manager)
    return
}

請注意,如果您決定使用orm.DB ,這是合理的,但它缺少您需要的一些方法並且已經由pg.DBpg.Tx ,那么您可以將orm.DB嵌入到您的自定義接口並僅添加那些缺少的方法。

type DB interface {
    Begin() (*Tx, error)
    orm.DB
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM