Generic GORM
Have you ever wanted to streamline your GORM repository implementations? With Go generics, you can write reusable methods that work across all your models. Let’s dive into an exciting journey of creating a generic repository layer!
🏗️ Step 1: Create a Base Repository
Install package harryosmar/generic-gorm
go get github.com/harryosmar/generic-gorm
🛠️ Step 2: Define Your Model
Let’s create a simple DummyModel and implement the required methods.
dummy_model.go
package main
type DummyModel struct {
Id int64 `json:"id" gorm:"column:id"`
Field1 string `json:"field_1" gorm:"column:field_1"`
}
func (t DummyModel) TableName() string {
return "dummy"
}
func (t DummyModel) PrimaryKey() string {
return "id"
}
🧩 Step 3: Create a Repository for Your Model
Now, let's tie the DummyModel with our generic base repository.
dummy_repository.go
package main
import (
"context"
"github.com/harryosmar/generic-gorm/base"
log "github.com/sirupsen/logrus"
"gorm.io/gorm"
)
type MySQLDummyRepository struct {
*base.BaseGorm[DummyEntities, int64]
}
func NewMySQLDummyRepository(db *gorm.DB) *MySQLDummyRepository {
return &MySQLDummyRepository{
base.NewBaseGorm[DummyModel, int64](db),
}
}
// Create MySQLDummyRepository with inherited methods from ./base/core.go :
// Gorm with generic with methods :
// - (o *BaseGorm[T, PkType]) Detail(ctx context.Context, id PkType) (*T, error)
// - (o *BaseGorm[T, PkType]) Wheres(ctx context.Context, wheres []Where)
// - (o *BaseGorm[T, PkType]) WheresList(ctx context.Context, orders []OrderBy, wheres []Where) ([]T, error)
// - (o *BaseGorm[T, PkType]) List(ctx context.Context, page int, pageSize int, orders []OrderBy, wheres []Where) ([]T, *Paginator, error)
// - (o *BaseGorm[T, PkType]) Create(ctx context.Context, row *T) (*T, error)
// - (o *BaseGorm[T, PkType]) CreateMultiple(ctx context.Context, rows []*T) ([]*T, int64, error)
// - (o *BaseGorm[T, PkType]) Update(ctx context.Context, row *T, updatedColumns []string) (int64, error)
// - (o *BaseGorm[T, PkType]) UpdateWhere(ctx context.Context, wheres []Where, values map[string]interface{}) (int64, error)
// - (o *BaseGorm[T, PkType]) Upsert(ctx context.Context, row *T, onConflictUpdatedColumns []string) (int64, error)
// - (o *BaseGorm[T, PkType]) ListCustom(ctx context.Context, page int, pageSize int, orders []OrderBy, wheres []Where, customCallback ListCustomCallback) ([]T, *Paginator, error)
✨ Step 4: Use Your Repository
Here's where things get interesting! Let's see how easy it is to interact with the database using our new repository.
Main Function Example
package main
import (
"context"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func main() {
// Initialize GORM with SQLite (or your database of choice)
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
log.Fatalf("Failed to connect to the database: %v", err)
}
// Auto-migrate the DummyModel
_ = db.AutoMigrate(&DummyModel{})
// Initialize the repository
repo := NewMySQLDummyRepository(db)
// Create a new DummyModel record
newDummy := &DummyModel{Field1: "Hello, Generics!"}
createdDummy, err := repo.Create(context.Background(), newDummy)
if err != nil {
log.Fatalf("Failed to create record: %v", err)
}
log.Printf("Created DummyModel: %+v", createdDummy)
// Fetch a record by ID
detail, err := repo.Detail(context.Background(), createdDummy.ID)
if err != nil {
log.Fatalf("Failed to fetch record: %v", err)
}
log.Printf("Fetched DummyModel: %+v", detail)
}
🚀 Explore More Features
Your generic base repository can include powerful features like:
- Filtering: Easily fetch records with dynamic WHERE conditions.
- Pagination: Add support for paginated lists.
- Upserts: Simplify inserting or updating records.
- Custom Queries: Use GORM's raw queries alongside generics.
Here are the generic methods provided by BaseGorm:
- Detail(ctx, id)
- Create(ctx, row)
- Update(ctx, row, updatedColumns)
- Upsert(ctx, row, onConflictColumns)
- List(ctx, page, pageSize, orders, wheres)
Feel free to explore and extend these methods based on your needs!
🌟 Why Use This Approach?
- Reusability: Write once, use for all models.
- Maintainability: Centralize common logic, making your codebase cleaner.
- Type Safety: Generics ensure your code is safe and predictable.
Now, go ahead and power up your GORM-based applications with this dynamic and reusable repository pattern! 🙌
