gofnext

package module
v0.0.20 Latest Latest
Warning

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

Go to latest
Published: Jan 10, 2024 License: MIT Imports: 19 Imported by: 0

README

🛠️ 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

function decorator
func f() res gofnext.CacheFn0(f)
func f(a) res gofnext.CacheFn1(f)
func f(a,b) res gofnext.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:

Key Description
TTL Cache Time to Live
CacheMap Custom own cache
NeedCacheIfErr Enable cache even if there is an error
HashKeyPointerAddr Use Pointer Addr as key instead of its value when hashing key
HashKeyFunc Custom 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

Documentation

Index

Constants

This section is empty.

Variables

View Source
var VERSION string = "v0.0.0"

Functions

func CacheFn0

func CacheFn0[V any](
	getFunc func() V,
	configs ...*Config,
) func() V

Cache Function with 0 parameter

func CacheFn0Err

func CacheFn0Err[V any](
	getFunc func() (V, error),
	configs ...*Config,
) func() (V, error)

Cache Function with 0 parameter

func CacheFn1

func CacheFn1[K any, V any](
	getFunc func(K) V,
	configs ...*Config,
) func(K) V

func CacheFn1Err

func CacheFn1Err[K any, V any](
	getFunc func(K) (V, error),
	configs ...*Config,
) func(K) (V, error)

Cache Function with 1 parameter

func CacheFn2

func CacheFn2[K1 any, K2 any, V any](
	getFunc func(K1, K2) V,
	configs ...*Config,
) func(K1, K2) V

Cache Function with 2 parameter

func CacheFn2Err

func CacheFn2Err[K1 any, K2 any, V any](
	getFunc func(K1, K2) (V, error),
	configs ...*Config,
) func(K1, K2) (V, error)

Cache Function with 2 parameter

func NewCacheLru

func NewCacheLru(maxSize int) *cacheLru

func NewCacheRedis

func NewCacheRedis(mapKey string) *redisMap

Types

type CacheMap

type CacheMap interface {
	// Goroutine concurrently on **same key**.
	Store(key, value any, err error)
	Load(key any) (value any, existed bool, err error)
	SetTTL(ttl time.Duration) CacheMap
	NeedMarshal() bool
}

type Config

type Config struct {
	TTL                time.Duration
	CacheMap           CacheMap
	NeedDumpKey        bool
	NeedCacheIfErr     bool
	HashKeyPointerAddr bool
	HashKeyFunc        func(args ...any) []byte
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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