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

Use db transactions #10

Open
YiNNx opened this issue Apr 11, 2023 · 3 comments
Open

Use db transactions #10

YiNNx opened this issue Apr 11, 2023 · 3 comments

Comments

@YiNNx
Copy link
Collaborator

YiNNx commented Apr 11, 2023

// TODO
// 这里有无更好的写法?
func GetModel() *Model {
return &Model{db}
}

You can encapsulate db transactions in Model

@ligen131
Copy link

ligen131 commented Apr 11, 2023

Do you mean to encapsulate all common database operations (CRUD) into Model? Some projects seem to do CRUD separately, and then it will create a global variable db *gorm.DB under the shared/database package to replace GetModel(). 🤔

See:

https://github.com/josephspurrier/gowebapp/blob/1f05cb0701a71bc4185817e73bca0e8c43408a39/vendor/app/shared/database/database.go#L15-L24

Which way is better?

@YiNNx
Copy link
Collaborator Author

YiNNx commented Apr 12, 2023

Do you mean to encapsulate all common database operations (CRUD) into Model? Some projects seem to do CRUD separately, and then it will create a global variable db *gorm.DB under the shared/database package to replace GetModel(). thinking

See:

https://github.com/josephspurrier/gowebapp/blob/1f05cb0701a71bc4185817e73bca0e8c43408a39/vendor/app/shared/database/database.go#L15-L24

Which way is better?

When using database transactions, we usually do database operations like this:

func CreateAnimals(db *gorm.DB) error {
  // Note the use of tx as the database handle once you are within a transaction
  tx := db.Begin()
  defer func() {
    if r := recover(); r != nil {
      tx.Rollback()
    }
  }()

  if err := tx.Error; err != nil {
    return err
  }

  if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
     tx.Rollback()
     return err
  }

  if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
     tx.Rollback()
     return err
  }

  return tx.Commit().Error
}

https://gorm.io/docs/transactions.html#A-Specific-Example

That means we use a seperate tx (db transaction) for a set of operations to ensure data consistency. So encapsulating it in Model will be a good idea, and we can abstract tx operations like this:

	m := model.GetModel()
	defer m.Close()

	err = m.CreateSomething()
	if err != nil {
		m.Abort() // rollback
		ErrHandle()
	}

	err = m.AddSomeRelation()
	if err != nil {
		m.Abort()  // rollback
		ErrHandle()
	}

	err = m.Commit() // if there's no err, then commit
        // ....
var db *gorm.DB

type model struct {
	tx      *gorm.DB
	context context.Context
	cancel  context.CancelFunc
}

func GetModel() *model {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
	m := &model{
		tx:      db.Begin().WithContext(ctx),
		context: ctx,
		cancel:  cancel,
	}
	return m
}

func (m *model) Close() {
	if r := recover(); r != nil {
		m.tx.Rollback()
	}
	m.cancel()
}

func (m *model) Abort() {
	m.tx.Rollback()
	m.cancel()
}

When there's no tx to use, I believe it's reasonable and also more convenient to do CRUD separately. But when using tx, maybe it's better to encapsulate it into Model. 🤔

@ligen131

@ligen131
Copy link

Understood. Thank you.

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

No branches or pull requests

2 participants