GORM transaction integration with Kratos, provides transaction functions with two-error-return pattern.
🎯 Two-Error Pattern: Distinguishes business logic errors and database transaction errors ⚡ Context Support: Built-in context timeout and cancellation handling 🔄 Auto Rollback: Transaction rollback on business logic errors 🌍 Kratos Integration: Smooth integration with Kratos microservice framework 📋 Simple API: Clean and concise transaction wrap functions
go get github.com/yylego/kratos-gorm/gormkratosThis example shows the simplest use of gormkratos.Transaction.
package main
import (
"context"
"fmt"
"github.com/go-kratos/kratos/v2/errors"
"github.com/google/uuid"
"github.com/yylego/kratos-gorm/gormkratos"
"github.com/yylego/must"
"github.com/yylego/rese"
"github.com/yylego/zaplog"
"go.uber.org/zap"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
type Admin struct {
ID uint `gorm:"primarykey"`
Name string `gorm:"not null"`
}
func main() {
dsn := fmt.Sprintf("file:db-%s?mode=memory&cache=shared", uuid.New().String())
db := rese.P1(gorm.Open(sqlite.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
}))
defer rese.F0(rese.P1(db.DB()).Close)
must.Done(db.AutoMigrate(&Admin{}))
ctx := context.Background()
erk := Transaction(ctx, db, func(db *gorm.DB) *errors.Error {
admin := &Admin{Name: "Alice"}
if err := db.Create(admin).Error; err != nil {
return ErrorServerDbError("create failed: %v", err)
}
zaplog.LOG.Debug("Created admin", zap.Uint("id", admin.ID), zap.String("name", admin.Name))
return nil
})
if erk != nil {
zaplog.LOG.Error("Error", zap.Error(erk))
}
}
func ErrorServerDbError(format string, args ...interface{}) *errors.Error {
return errors.New(500, "DB_ERROR", fmt.Sprintf(format, args...))
}
func ErrorServerDbTransactionError(format string, args ...interface{}) *errors.Error {
return errors.New(500, "TRANSACTION_ERROR", fmt.Sprintf(format, args...))
}
func Transaction(ctx context.Context, db *gorm.DB, run func(db *gorm.DB) *errors.Error) *errors.Error {
erk, err := gormkratos.Transaction(ctx, db, run)
if err != nil {
if erk != nil {
return erk
}
return ErrorServerDbTransactionError("transaction failed: %v", err)
}
return nil
}⬆️ Source: Source
This example shows auto rollback when business logic returns errors.
package main
import (
"context"
"fmt"
"github.com/go-kratos/kratos/v2/errors"
"github.com/google/uuid"
"github.com/yylego/kratos-gorm/gormkratos"
"github.com/yylego/must"
"github.com/yylego/rese"
"github.com/yylego/zaplog"
"go.uber.org/zap"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
type Guest struct {
ID uint `gorm:"primarykey"`
Name string `gorm:"not null"`
}
func main() {
dsn := fmt.Sprintf("file:db-%s?mode=memory&cache=shared", uuid.New().String())
db := rese.P1(gorm.Open(sqlite.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
}))
defer rese.F0(rese.P1(db.DB()).Close)
must.Done(db.AutoMigrate(&Guest{}))
ctx := context.Background()
erk := Transaction(ctx, db, func(db *gorm.DB) *errors.Error {
guest := &Guest{Name: "Bob"}
if err := db.Create(guest).Error; err != nil {
return ErrorServerDbError("create failed: %v", err)
}
zaplog.LOG.Debug("Created guest (then rollback)", zap.Uint("id", guest.ID), zap.String("name", guest.Name))
return ErrorBadRequest("validation failed")
})
zaplog.LOG.Error("Error", zap.Error(erk))
var count int64
db.Model(&Guest{}).Count(&count)
zaplog.LOG.Debug("Guest count post rollback", zap.Int64("count", count))
}
func ErrorServerDbError(format string, args ...interface{}) *errors.Error {
return errors.New(500, "DB_ERROR", fmt.Sprintf(format, args...))
}
func ErrorBadRequest(format string, args ...interface{}) *errors.Error {
return errors.New(400, "BAD_REQUEST", fmt.Sprintf(format, args...))
}
func ErrorServerDbTransactionError(format string, args ...interface{}) *errors.Error {
return errors.New(500, "TRANSACTION_ERROR", fmt.Sprintf(format, args...))
}
func Transaction(ctx context.Context, db *gorm.DB, run func(db *gorm.DB) *errors.Error) *errors.Error {
erk, err := gormkratos.Transaction(ctx, db, run)
if err != nil {
if erk != nil {
return erk
}
return ErrorServerDbTransactionError("transaction failed: %v", err)
}
return nil
}⬆️ Source: Source
This example shows combining create and update in one atomic transaction.
package main
import (
"context"
"fmt"
"github.com/go-kratos/kratos/v2/errors"
"github.com/google/uuid"
"github.com/yylego/kratos-gorm/gormkratos"
"github.com/yylego/must"
"github.com/yylego/rese"
"github.com/yylego/zaplog"
"go.uber.org/zap"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
type Product struct {
ID uint `gorm:"primarykey"`
Name string `gorm:"not null"`
Price int
}
func main() {
dsn := fmt.Sprintf("file:db-%s?mode=memory&cache=shared", uuid.New().String())
db := rese.P1(gorm.Open(sqlite.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
}))
defer rese.F0(rese.P1(db.DB()).Close)
must.Done(db.AutoMigrate(&Product{}))
ctx := context.Background()
erk := Transaction(ctx, db, func(db *gorm.DB) *errors.Error {
product := &Product{Name: "Laptop", Price: 5000}
if err := db.Create(product).Error; err != nil {
return ErrorServerDbError("create failed: %v", err)
}
zaplog.LOG.Debug("Created product", zap.Uint("id", product.ID), zap.String("name", product.Name), zap.Int("price", product.Price))
product.Price = 4500
if err := db.Updates(product).Error; err != nil {
return ErrorServerDbError("update failed: %v", err)
}
zaplog.LOG.Debug("Updated product", zap.Uint("id", product.ID), zap.String("name", product.Name), zap.Int("price", product.Price))
return nil
})
if erk != nil {
zaplog.LOG.Error("Error", zap.Error(erk))
}
}
func ErrorServerDbError(format string, args ...interface{}) *errors.Error {
return errors.New(500, "DB_ERROR", fmt.Sprintf(format, args...))
}
func ErrorServerDbTransactionError(format string, args ...interface{}) *errors.Error {
return errors.New(500, "TRANSACTION_ERROR", fmt.Sprintf(format, args...))
}
func Transaction(ctx context.Context, db *gorm.DB, run func(db *gorm.DB) *errors.Error) *errors.Error {
erk, err := gormkratos.Transaction(ctx, db, run)
if err != nil {
if erk != nil {
return erk
}
return ErrorServerDbTransactionError("transaction failed: %v", err)
}
return nil
}⬆️ Source: Source
The gormkratos.Transaction function returns two errors to help distinguish between different types:
- Business Logic Errors (
erk *errors.Error): Kratos framework errors from business logic - Database Transaction Errors (
err error): Database transaction errors
⚠️ IMPORTANT:When
err != nilanderk != nil,erkcontains the specific business reason. Returnerkfirst since it has more business context (reason and code) than what the raw transaction throws.
Always use this pattern:
erk, err := gormkratos.Transaction(ctx, db, func(db *gorm.DB) *errors.Error {
// your business logic
return nil
})
if err != nil {
if erk != nil {
return erk
}
return YourTransactionError("transaction failed: %v", err)
}When err != nil:
erk != nil: Business logic error caused rollback (useerk)erk == nil: Database commit failed (wraperr)
When err == nil:
erkalso nil, both succeeded
Direct use of gormkratos.Transaction:
erk, err := gormkratos.Transaction(ctx, db, func(db *gorm.DB) *errors.Error {
user := &User{Name: "test"}
if err := db.Create(user).Error; err != nil {
return errorspb.ErrorServerDbError("create failed: %v", err)
}
return nil
})Check business errors:
if erk != nil {
// Handle Kratos business errors
log.Printf("Business logic failed: %v", erk)
}Check database errors:
if err != nil {
// Handle database transaction errors
log.Printf("Database transaction failed: %v", err)
}Set transaction isolation:
import "database/sql"
erk, err := gormkratos.Transaction(ctx, db, func(db *gorm.DB) *errors.Error {
// Transaction logic with custom isolation
return nil
}, &sql.TxOptions{
Isolation: sql.LevelReadCommitted,
ReadOnly: false,
})Combine create and update:
erk, err := gormkratos.Transaction(ctx, db, func(db *gorm.DB) *errors.Error {
product := &Product{Name: "Laptop", Price: 5000}
if err := db.Create(product).Error; err != nil {
return ErrorServerDbError("create failed: %v", err)
}
product.Price = 4500
if err := db.Updates(product).Error; err != nil {
return ErrorServerDbError("update failed: %v", err)
}
return nil
})Auto rollback on timeout:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
erk, err := gormkratos.Transaction(ctx, db, func(db *gorm.DB) *errors.Error {
// Long-running operations
time.Sleep(10 * time.Second) // Exceed timeout
return nil
})
// err will contain timeout errorsMIT License - see LICENSE.
Contributions are welcome! Report bugs, suggest features, and contribute code:
- 🐛 Mistake reports? Open an issue on GitHub with reproduction steps
- 💡 Fresh ideas? Create an issue to discuss
- 📖 Documentation confusing? Report it so we can improve
- 🚀 Need new features? Share the use cases to help us understand requirements
- ⚡ Performance issue? Help us optimize through reporting slow operations
- 🔧 Configuration problem? Ask questions about complex setups
- 📢 Follow project progress? Watch the repo to get new releases and features
- 🌟 Success stories? Share how this package improved the workflow
- 💬 Feedback? We welcome suggestions and comments
New code contributions, follow this process:
- Fork: Fork the repo on GitHub (using the webpage UI).
- Clone: Clone the forked project (
git clone https://github.com/yourname/repo-name.git). - Navigate: Navigate to the cloned project (
cd repo-name) - Branch: Create a feature branch (
git checkout -b feature/xxx). - Code: Implement the changes with comprehensive tests
- Testing: (Golang project) Ensure tests pass (
go test ./...) and follow Go code style conventions - Documentation: Update documentation to support client-facing changes
- Stage: Stage changes (
git add .) - Commit: Commit changes (
git commit -m "Add feature xxx") ensuring backward compatible code - Push: Push to the branch (
git push origin feature/xxx). - PR: Open a merge request on GitHub (on the GitHub webpage) with detailed description.
Please ensure tests pass and include relevant documentation updates.
Welcome to contribute to this project via submitting merge requests and reporting issues.
Project Support:
- ⭐ Give GitHub stars if this project helps you
- 🤝 Share with teammates and (golang) programming friends
- 📝 Write tech blogs about development tools and workflows - we provide content writing support
- 🌟 Join the ecosystem - committed to supporting open source and the (golang) development scene
Have Fun Coding with this package! 🎉🎉🎉