Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update: *DB and Tx implement tha same interface "QueryExecuter" #933

Conversation

toyohashi6140
Copy link

@max107

Hello! I just wanted to make one suggestion, and I'm a PR.

*DB and Tx to implement a common interface "QueryExecuter". Otherwise, if we want to handle transactions in the usecase layer in clean architecture, etc., the usecase must directly hold *bun.

*DB and bun.Tx to implement a common interface, it would be easier to use transaction processing in the usecase layer by using context.

For a concrete example, look at the following sample code(For the sake of simplification, usecase and repository are not interfaces but structs.)

repository layer

type ( 
  DBExecuter interface {
      GetDB(context.Context) bun.QueryExecuter
  }
  dbExecuterChild struct {
      db *bun.DB
  }
)
var txKey struct{}

func DoInTx[T any](ctx context.Context, db *bun.DB, f func(context.Context)(T,error)){
  var result T
  tx, err := db.BeginTx(ctx)
  if err != nil {
    return T, err
  }
  ctx = context.WithValue(ctx, txKey, tx)
  result, err = f(ctx)
  if err != nil {
    if err := tx.RollBack(); err != nil {
      return result, err
    }
    return result, err
  }
  if err := tx.Commit(); err != nil {
    return result, err
  }
  return result, nil
}

func NewDBExecuter(db *bun.DB) DBExecuter {
    return &dbExecuterChild{db: db}
}

func(de *dbExecuterChild) GetDB(ctx context.Context) bun.QueryExecuter {
  tx, ok := ctx.Value(txKey).(bun.Tx)
  if ok {
       return tx
  }
  return de.db
}

type (
  RepositoryA struct {
      db DBExecuter
  } 
  SampleData struct {
    ID int64 `bun:"id"`
    Name string `bun:"name"`
  }
)
func (r *RepositoryA) GetDB() bun.QueryExecuter {
  return r.db
}
func (r *RepositoryA) GetData(ctx context.Context, model SampleData) (SampleData, error){
    if err := r.db.GetDB(ctx).NewSelect().Model(&model).Scan(ctx); err != nil {
        return nil, err
    }
    return model, nil
}
func (r *RepositoryA) InsertData(ctx context.Context, model SampleData)  error {
    return r.db.GetDB(ctx).NewInsert().Model(&model).Exec(ctx)
}

usecase layer

type (
  UseCase struct {
      repository repository.RepositoryA
  }
)

func(u *UseCase) GetData(ctx context.Context)( repositry.SampleData, error){
  result, err := u.repository.GetData(ctx, repositry.SampleData{})
  ...
}
func(u *UseCase) InsertData(ctx context.Context) error {
  return repository.DoInTx(ctx, u.repository.GetDB(), f func(ctx context.Context)(repositry.SampleData ,error)){
    result, err := u.repositoryA.InsertData(ctx, repositry.SampleData{ID: 1, Name: "sample"})
    ...
    return nil
  }
}

If you have any questions about something you are not sure about, please ask!

@toyohashi6140 toyohashi6140 force-pushed the feature/wrap-interface-db-and-tx branch from e299c3a to edb4d83 Compare November 19, 2023 06:22
@vmihailenco
Copy link
Member

@toyohashi6140 did you see the IDB interface? It seems to do the same already

// IDB is a common interface for *bun.DB, bun.Conn, and bun.Tx.
type IDB interface {
	IConn
	Dialect() schema.Dialect

	NewValues(model interface{}) *ValuesQuery
	NewSelect() *SelectQuery
	NewInsert() *InsertQuery
	NewUpdate() *UpdateQuery
	NewDelete() *DeleteQuery
	NewRaw(query string, args ...interface{}) *RawQuery
	NewCreateTable() *CreateTableQuery
	NewDropTable() *DropTableQuery
	NewCreateIndex() *CreateIndexQuery
	NewDropIndex() *DropIndexQuery
	NewTruncateTable() *TruncateTableQuery
	NewAddColumn() *AddColumnQuery
	NewDropColumn() *DropColumnQuery

	BeginTx(ctx context.Context, opts *sql.TxOptions) (Tx, error)
	RunInTx(ctx context.Context, opts *sql.TxOptions, f func(ctx context.Context, tx Tx) error) error
}

@vmihailenco vmihailenco added the question Further information is requested label Jan 6, 2024
@toyohashi6140
Copy link
Author

@vmihailenco I missed it, thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants