caches

package module
v3.0.0 Latest Latest
Warning

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

Go to latest
Published: Jan 17, 2024 License: MIT Imports: 9 Imported by: 0

README

Gorm Caches

Gorm Caches plugin using database request reductions (easer), and response caching mechanism provide you an easy way to optimize database performance.

Features

  • Database request reduction. If three identical requests are running at the same time, only the first one is going to be executed, and its response will be returned for all.
  • Database response caching. By implementing the Cacher interface, you can easily setup a caching mechanism for your database queries.
  • Supports all databases that are supported by gorm itself.

Install

go get -u github.com/go-gorm/caches/v2

Usage

Configure the easer, and the cacher, and then load the plugin to gorm.

package main

import (
	"fmt"
	"sync"

	"github.com/go-gorm/caches"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

func main() {
	db, _ := gorm.Open(
		mysql.Open("DATABASE_DSN"),
		&gorm.Config{},
	)
	cachesPlugin := &caches.Caches{Conf: &caches.Config{
		Easer: true,
		Cacher: &yourCacherImplementation{},
	}}
	_ = db.Use(cachesPlugin)
}

Easer Example

package main

import (
	"fmt"
	"sync"
	"time"

	"github.com/go-gorm/caches"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

type UserRoleModel struct {
	gorm.Model
	Name string `gorm:"unique"`
}

type UserModel struct {
	gorm.Model
	Name   string
	RoleId uint
	Role   *UserRoleModel `gorm:"foreignKey:role_id;references:id"`
}

func main() {
	db, _ := gorm.Open(
		mysql.Open("DATABASE_DSN"),
		&gorm.Config{},
	)

	cachesPlugin := &caches.Caches{Conf: &caches.Config{
		Easer: true,
	}}

	_ = db.Use(cachesPlugin)

	_ = db.AutoMigrate(&UserRoleModel{})

	_ = db.AutoMigrate(&UserModel{})

	adminRole := &UserRoleModel{
		Name: "Admin",
	}
	db.FirstOrCreate(adminRole, "Name = ?", "Admin")

	guestRole := &UserRoleModel{
		Name: "Guest",
	}
	db.FirstOrCreate(guestRole, "Name = ?", "Guest")

	db.Save(&UserModel{
		Name: "ktsivkov",
		Role: adminRole,
	})
	db.Save(&UserModel{
		Name: "anonymous",
		Role: guestRole,
	})

	var (
		q1Users []UserModel
		q2Users []UserModel
	)
	wg := &sync.WaitGroup{}
	wg.Add(2)
	go func() {
		db.Model(&UserModel{}).Joins("Role").Find(&q1Users, "Role.Name = ? AND Sleep(1) = false", "Admin")
		wg.Done()
	}()
	go func() {
		time.Sleep(500 * time.Millisecond)
		db.Model(&UserModel{}).Joins("Role").Find(&q2Users, "Role.Name = ? AND Sleep(1) = false", "Admin")
		wg.Done()
	}()
	wg.Wait()

	fmt.Println(fmt.Sprintf("%+v", q1Users))
	fmt.Println(fmt.Sprintf("%+v", q2Users))
}

Cacher Example (Redis)

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/go-gorm/caches/v3"
	"github.com/redis/go-redis/v9"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

type UserRoleModel struct {
	gorm.Model
	Name string `gorm:"unique"`
}

type UserModel struct {
	gorm.Model
	Name   string
	RoleId uint
	Role   *UserRoleModel `gorm:"foreignKey:role_id;references:id"`
}

type redisCacher struct {
	rdb *redis.Client
}

func (c *redisCacher) Get(ctx context.Context, key string, q *caches.Query[any]) (*caches.Query[any], error) {
	res, err := c.rdb.Get(ctx, key).Result()
	if err == redis.Nil {
		return nil, nil
	}

	if err != nil {
		return nil, err
	}

	if err := q.Unmarshal([]byte(res)); err != nil {
		return nil, err
	}

	return q, nil
}

func (c *redisCacher) Store(ctx context.Context, key string, val *caches.Query[any]) error {
	res, err := val.Marshal()
	if err != nil {
		return err
	}

	c.rdb.Set(ctx, key, res, 300*time.Second) // Set proper cache time
	return nil
}

func main() {
	db, _ := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})

	cachesPlugin := &caches.Caches{Conf: &caches.Config{
		Cacher: &redisCacher{
			rdb: redis.NewClient(&redis.Options{
				Addr:     "localhost:6379",
				Password: "",
				DB:       0,
			}),
		},
	}}

	_ = db.Use(cachesPlugin)

	_ = db.AutoMigrate(&UserRoleModel{})
	_ = db.AutoMigrate(&UserModel{})

	db.Delete(&UserRoleModel{})
	db.Delete(&UserModel{})

	adminRole := &UserRoleModel{
		Name: "Admin",
	}
	db.Save(adminRole)

	guestRole := &UserRoleModel{
		Name: "Guest",
	}
	db.Save(guestRole)

	db.Save(&UserModel{
		Name: "ktsivkov",
		Role: adminRole,
	})

	db.Save(&UserModel{
		Name: "anonymous",
		Role: guestRole,
	})

	q1User := &UserModel{}
	db.WithContext(context.Background()).Find(q1User, "Name = ?", "ktsivkov")
	q2User := &UserModel{}
	db.WithContext(context.Background()).Find(q2User, "Name = ?", "ktsivkov")

	fmt.Println(fmt.Sprintf("%+v", q1User))
	fmt.Println(fmt.Sprintf("%+v", q2User))
}

Cacher Example (Memory)

package main

import (
	"context"
	"fmt"
	"sync"

	"github.com/go-gorm/caches/v3"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

type UserRoleModel struct {
	gorm.Model
	Name string `gorm:"unique"`
}

type UserModel struct {
	gorm.Model
	Name   string
	RoleId uint
	Role   *UserRoleModel `gorm:"foreignKey:role_id;references:id"`
}

type memoryCacher struct {
	store *sync.Map
}

func (c *memoryCacher) init() {
	if c.store == nil {
		c.store = &sync.Map{}
	}
}

func (c *memoryCacher) Get(ctx context.Context, key string, q *caches.Query[any]) (*caches.Query[any], error) {
	c.init()
	val, ok := c.store.Load(key)
	if !ok {
		return nil, nil
	}

	if err := q.Unmarshal(val.([]byte)); err != nil {
		return nil, err
	}

	return q, nil
}

func (c *memoryCacher) Store(ctx context.Context, key string, val *caches.Query[any]) error {
	c.init()
	res, err := val.Marshal()
	if err != nil {
		return err
	}

	c.store.Store(key, res)
	return nil
}

func main() {
	db, _ := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})

	cachesPlugin := &caches.Caches{Conf: &caches.Config{
		Cacher: &memoryCacher{},
	}}

	_ = db.Use(cachesPlugin)

	_ = db.AutoMigrate(&UserRoleModel{})
	_ = db.AutoMigrate(&UserModel{})

	db.Delete(&UserRoleModel{})
	db.Delete(&UserModel{})

	adminRole := &UserRoleModel{
		Name: "Admin",
	}
	db.Save(adminRole)

	guestRole := &UserRoleModel{
		Name: "Guest",
	}
	db.Save(guestRole)

	db.Save(&UserModel{
		Name: "ktsivkov",
		Role: adminRole,
	})

	db.Save(&UserModel{
		Name: "anonymous",
		Role: guestRole,
	})

	q1User := &UserModel{}
	db.WithContext(context.Background()).Find(q1User, "Name = ?", "ktsivkov")
	q2User := &UserModel{}
	db.WithContext(context.Background()).Find(q2User, "Name = ?", "ktsivkov")

	fmt.Println(fmt.Sprintf("%+v", q1User))
	fmt.Println(fmt.Sprintf("%+v", q2User))
}

License

MIT license.

Easer

The easer is an adjusted version of the ServantGo library to fit the needs of this plugin.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func SetPointedValue

func SetPointedValue(dest interface{}, src interface{})

Types

type Cacher

type Cacher interface {
	// Get impl should check if a specific key exists in the cache and return its value
	// look at Query.Marshal
	Get(ctx context.Context, key string, q *Query[any]) (*Query[any], error)
	// Store is supposed to store a cached representation of the val param
	// look at Query.Unmarshal
	Store(ctx context.Context, key string, val *Query[any]) error
}

type Caches

type Caches struct {
	Conf *Config
	// contains filtered or unexported fields
}

func (*Caches) Initialize

func (c *Caches) Initialize(db *gorm.DB) error

func (*Caches) Name

func (c *Caches) Name() string

func (*Caches) Query

func (c *Caches) Query(db *gorm.DB)

type Config

type Config struct {
	Easer  bool
	Cacher Cacher
}

type Query

type Query[T any] struct {
	Dest         T
	RowsAffected int64
}

func (*Query[T]) Marshal

func (q *Query[T]) Marshal() ([]byte, error)

func (*Query[T]) Unmarshal

func (q *Query[T]) Unmarshal(bytes []byte) error

Jump to

Keyboard shortcuts

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