Generic GORM

GoORMGenericInheritance
Generic GORM

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! 🙌