Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

github.com/ahuigo/gocache-decorator

Package Overview
Dependencies
Alerts
File Explorer
Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

github.com/ahuigo/gocache-decorator

  • v0.0.20
  • Source
  • Go
  • Socket score

Version published
Created
Source

🛠️ Go function extended

tag Go Version GoDoc Build Status Go report Coverage Contributors License

This gofnext provides the following functions extended(go>=1.21).

Cache decorators(concurrent safe): Similar to Python's functools.cache and functools.lru_cache.

In addition to memory caching, it also supports Redis caching and custom caching.

简体中文

Decorator cases

functiondecorator
func f() resgofnext.CacheFn0(f)
func f(a) resgofnext.CacheFn1(f)
func f(a,b) resgofnext.CacheFn2(f)
func f() (res,err)gofnext.CacheFn0Err(f)
func f(a) (res,err)gofnext.CacheFn1Err(f)
func f(a,b) (res,err)gofnext.CacheFn2Err(f)
func f() (res,err)gofnext.CacheFn0Err(f, &gofnext.Config{TTL: time.Hour})
// memory cache with ttl
func f() (res)gofnext.CacheFn0(f, &gofnext.Config{CacheMap: gofnext.NewCacheLru(9999)})
// Maxsize of cache is 9999
func f() (res)gofnext.CacheFn0(f, &gofnext.Config{CacheMap: gofnext.NewCacheRedis("cacheKey")})
// Warning: redis's marshaling may result in data loss

Features

  • Cache Decorator (gofnext)
    • Decorator cache for function
    • Concurrent goroutine Safe
    • Support memory CacheMap(default)
    • Support memory-lru CacheMap
    • Support redis CacheMap
    • Support customization of the CacheMap(manually)

Decorator examples

Refer to: examples

Cache fibonacii function

Refer to: decorator fib example

package main
import "fmt"
import "github.com/ahuigo/gofnext"
func main() {
    var fib func(int) int
    fib = func(x int) int {
        fmt.Printf("call arg:%d\n", x)
        if x <= 1 {
            return x
        } else {
            return fib(x-1) + fib(x-2)
        }
    }
    fib = gofnext.CacheFn1(fib)

    fmt.Println(fib(5))
    fmt.Println(fib(6))
}

Cache function with 0 param

Refer to: decorator example

package examples

import "github.com/ahuigo/gofnext"

func getUserAnonymouse() (UserInfo, error) {
    fmt.Println("select * from db limit 1", time.Now())
    time.Sleep(10 * time.Millisecond)
    return UserInfo{Name: "Anonymous", Age: 9}, errors.New("db error")
}

var (
    // Cacheable Function
    getUserInfoFromDbWithCache = gofnext.CacheFn0Err(getUserAnonymouse) 
)

func TestCacheFuncWithNoParam(t *testing.T) {
    // Execute the function multi times in parallel.
    times := 10
    parallelCall(func() {
        userinfo, err := getUserInfoFromDbWithCache()
        fmt.Println(userinfo, err)
    }, times)
}

Cache function with 1 param

Refer to: decorator example

func getUserNoError(age int) (UserInfo) {
	time.Sleep(10 * time.Millisecond)
	return UserInfo{Name: "Alex", Age: age}
}

var (
	// Cacheable Function with 1 param and no error
	getUserInfoFromDbNil= gofnext.CacheFn1(getUserNoError) 
)

func TestCacheFuncNil(t *testing.T) {
	// Execute the function multi times in parallel.
	times := 10
	parallelCall(func() {
		userinfo := getUserInfoFromDbNil(20)
		fmt.Println(userinfo)
	}, times)
}

Cache function with 2 params

Refer to: decorator example

func TestCacheFuncWith2Param(t *testing.T) {
    // Original function
    executeCount := 0
    getUserScore := func(c context.Context, id int) (int, error) {
        executeCount++
        fmt.Println("select score from db where id=", id, time.Now())
        time.Sleep(10 * time.Millisecond)
        return 98 + id, errors.New("db error")
    }

    // Cacheable Function
    getUserScoreFromDbWithCache := gofnext.CacheFn2Err(getUserScore, &gofnext.Config{
        TTL: time.Hour,
    }) // getFunc can only accept 2 parameter

    // Execute the function multi times in parallel.
    ctx := context.Background()
    parallelCall(func() {
        score, _ := getUserScoreFromDbWithCache(ctx, 1)
        if score != 99 {
            t.Errorf("score should be 99, but get %d", score)
        }
        getUserScoreFromDbWithCache(ctx, 2)
        getUserScoreFromDbWithCache(ctx, 3)
    }, 10)

    if executeCount != 3 {
        t.Errorf("executeCount should be 3, but get %d", executeCount)
    }
}

Cache function with more params(>2)

Refer to: decorator example

executeCount := 0
type Stu struct {
	name   string
	age    int
	gender int
}

// Original function
fn := func(name string, age, gender int) int {
	executeCount++
	// select score from db where name=name and age=age and gender=gender
	switch name {
	case "Alex":
		return 10
	default:
		return 30
	}
}

// Convert to extra parameters to a single parameter(2 prameters is ok)
fnWrap := func(arg Stu) int {
	return fn(arg.name, arg.age, arg.gender)
}

// Cacheable Function
fnCachedInner := gofnext.CacheFn1(fnWrap)
fnCached := func(name string, age, gender int) int {
	return fnCachedInner(Stu{name, age, gender})
}

// Execute the function multi times in parallel.
parallelCall(func() {
	score := fnCached("Alex", 20, 1)
	if score != 10 {
		t.Errorf("score should be 10, but get %d", score)
	}
	fnCached("Jhon", 21, 0)
	fnCached("Alex", 20, 1)
}, 10)

// Test Count
if executeCount != 2 {
	t.Errorf("executeCount should be 2, but get %d", executeCount)
}

Cache function with lru cache

Refer to: decorator lru example

executeCount := 0
maxCacheSize := 2
var getUserScore = func(more int) (int, error) {
	executeCount++
	return 98 + more, errors.New("db error")
}

// Cacheable Function
var getUserScoreFromDbWithLruCache = gofnext.CacheFn1Err(getUserScore, &gofnext.Config{
	TTL:      time.Hour,
	CacheMap: gofnext.NewCacheLru(maxCacheSize),
})

Cache function with redis cache(unstable)

Warning: Since redis needs JSON marshaling, this may result in data loss.

Refer to: decorator redis example

var (
    // Cacheable Function
    getUserScoreFromDbWithCache = gofnext.CacheFn1Err(getUserScore, &gofnext.Config{
        TTL:  time.Hour,
        CacheMap: gofnext.NewCacheRedis("redis-cache-key"),
    }) 
)

func TestRedisCacheFuncWithTTL(t *testing.T) {
    // Execute the function multi times in parallel.
    for i := 0; i < 10; i++ {
        score, _ := getUserScoreFromDbWithCache(1)
        if score != 99 {
            t.Errorf("score should be 99, but get %d", score)
        }
    }
}

To avoid keys being too long, you can limit the length of Redis key:

cacheMap := gofnext.NewCacheRedis("redis-cache-key").SetMaxHashKeyLen(256);

Set redis config:

// method 1: by default: localhost:6379
cache := gofnext.NewCacheRedis("redis-cache-key") 

// method 2: set redis addr
cache.SetRedisAddr("192.168.1.1:6379")

// method 3: set redis options
cache.SetRedisOpts(&redis.Options{
	Addr: "localhost:6379",
})

// method 4: set redis universal options
cache.SetRedisUniversalOpts(&redis.UniversalOptions{
	Addrs: []string{"localhost:6379"},
})

Custom cache map

Refer to: https://github.com/ahuigo/gofnext/blob/main/cache-map-mem.go

Decorator config

Config item(gofnext.Config)

gofnext.Config item list:

KeyDescription
TTLCache Time to Live
CacheMapCustom own cache
NeedCacheIfErrEnable cache even if there is an error
HashKeyPointerAddrUse Pointer Addr as key instead of its value when hashing key
HashKeyFuncCustom hash key function

Cache Timeout

e.g.

gofnext.CacheFn1Err(getUserScore, &gofnext.Config{
    TTL:  time.Hour,
}) 

Do not cache if there is an error

By default, gofnext won't cache error when there is an error.

To use the cache even when there is an error, simply add NeedCacheIfErr: true. Refer to: https://github.com/ahuigo/gofnext/blob/main/examples/decorator-err_test.go

gofnext.CacheFn1Err(getUserScore, &gofnext.Config{
    NeedCacheIfErr: true,
}) 

Hash Pointer address or value?

Decorator will hash function's all parameters into hashkey. By default, if parameter is pointer, decorator will hash its real value instead of pointer address.

If you wanna hash pointer address, you should turn on HashKeyPointerAddr:

getUserScoreFromDbWithCache := gofnext.CacheFn1Err(getUserScore, &gofnext.Config{
	HashKeyPointerAddr: true,
})

Custom hash key function

In this case, you need to ensure that duplicate keys are not generated. Refer to: example

// hash key function
hashKeyFunc := func(keys ...any) []byte{
	user := keys[0].(*UserInfo)
	flag := keys[1].(bool)
	return []byte(fmt.Sprintf("user:%d,flag:%t", user.id, flag))
}

// Cacheable Function
getUserScoreFromDbWithCache := gofnext.CacheFn2Err(getUserScore, &gofnext.Config{
	HashKeyFunc: hashKeyFunc,
})

Roadmap

  • [] Include private property when serialization for redis

FAQs

Package last updated on 10 Jan 2024

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc