kratos-gorm

module
v0.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 11, 2026 License: MIT

README ΒΆ

GitHub Workflow Status (branch) GoDoc Coverage Status Supported Go Versions GitHub Release Go Report Card

kratos-gorm

GORM transaction integration with Kratos, provides transaction functions with two-error-return pattern.


CHINESE README

δΈ­ζ–‡θ―΄ζ˜Ž

Main Features

🎯 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

Install

go get github.com/yylego/kratos-gorm/gormkratos

Usage

Basic Transaction

This 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

Transaction Rollback

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

Multiple Operations

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

Error Handling

The gormkratos.Transaction function returns two errors to help distinguish between different types:

  1. Business Logic Errors (erk *errors.Error): Kratos framework errors from business logic
  2. Database Transaction Errors (err error): Database transaction errors

⚠️ IMPORTANT:

When err != nil and erk != nil, erk contains the specific business reason. Return erk first 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)
}
Scenarios

When err != nil:

  • erk != nil: Business logic error caused rollback (use erk)
  • erk == nil: Database commit failed (wrap err)

When err == nil:

  • erk also nil, both succeeded

Examples

Basic Two-Error Return

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)
}
With Transaction Options

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,
})
Multiple Operations in One Transaction

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
})
Context Timeout Handling

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 errors

πŸ“„ License

MIT License - see LICENSE.


πŸ’¬ Contact & Feedback

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

πŸ”§ Development

New code contributions, follow this process:

  1. Fork: Fork the repo on GitHub (using the webpage UI).
  2. Clone: Clone the forked project (git clone https://github.com/yourname/repo-name.git).
  3. Navigate: Navigate to the cloned project (cd repo-name)
  4. Branch: Create a feature branch (git checkout -b feature/xxx).
  5. Code: Implement the changes with comprehensive tests
  6. Testing: (Golang project) Ensure tests pass (go test ./...) and follow Go code style conventions
  7. Documentation: Update documentation to support client-facing changes
  8. Stage: Stage changes (git add .)
  9. Commit: Commit changes (git commit -m "Add feature xxx") ensuring backward compatible code
  10. Push: Push to the branch (git push origin feature/xxx).
  11. PR: Open a merge request on GitHub (on the GitHub webpage) with detailed description.

Please ensure tests pass and include relevant documentation updates.


🌟 Support

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! πŸŽ‰πŸŽ‰πŸŽ‰


GitHub Stars

Stargazers

Directories ΒΆ

Path Synopsis
Package gormkratos: GORM transaction integration with Kratos Provides two-error-return pattern to distinguish business logic errors and database errors
Package gormkratos: GORM transaction integration with Kratos Provides two-error-return pattern to distinguish business logic errors and database errors
internal
demos/demo1x command
demo1x: Basic transaction with success case Demonstrates the simplest usage of gormkratos.Transaction
demo1x: Basic transaction with success case Demonstrates the simplest usage of gormkratos.Transaction
demos/demo2x command
demo2x: Transaction rollback on business logic errors Demonstrates auto rollback when business logic returns errors
demo2x: Transaction rollback on business logic errors Demonstrates auto rollback when business logic returns errors
demos/demo3x command
demo3x: Create and update operations in single transaction Demonstrates combining multiple database operations within one atomic transaction
demo3x: Create and update operations in single transaction Demonstrates combining multiple database operations within one atomic transaction

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL