gofnext

package module
v0.0.24 Latest Latest
Warning

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

Go to latest
Published: Dec 9, 2024 License: MIT Imports: 19 Imported by: 2

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() R gofnext.CacheFn0(f)
func f(K) R gofnext.CacheFn1(f)
func f(K1, K2) R gofnext.CacheFn2(f)
func f() (R,error) gofnext.CacheFn0Err(f)
func f(T) (R,error) gofnext.CacheFn1Err(f)
func f(T,P) (R,error) gofnext.CacheFn2Err(f)
func f() (R,error) gofnext.CacheFn0Err(f, &gofnext.Config{TTL: time.Hour})
// memory cache with ttl
func f() R gofnext.CacheFn0(f, &gofnext.Config{CacheMap: gofnext.NewCacheLru(9999)})
// Maxsize of cache is 9999
func f() R gofnext.CacheFn0(f, &gofnext.Config{CacheMap: gofnext.NewCacheRedis("cacheKey")})
// Warning: redis's marshaling may result in data loss

Benchmark Benchmark case: https://github.com/ahuigo/gofnext/blob/main/bench/

# golang1.22
pkg: github.com/ahuigo/gofnext/bench
BenchmarkGetDataWithNoCache-10               100          11179015 ns/op          281220 B/op         99 allocs/op
BenchmarkGetDataWithMemCache-10         11036955                95.49 ns/op           72 B/op          2 allocs/op
BenchmarkGetDataWithLruCache-10         11362039               104.8 ns/op            72 B/op          2 allocs/op
BenchmarkGetDataWithRedisCache-10          15850             74653 ns/op           28072 B/op         29 allocs/op

Features

  • Cache Decorator (gofnext)
    • Decorator cache for function
    • Concurrent goroutine Safe
    • Support memory CacheMap(default)
    • Support memory-lru CacheMap
    • Support redis CacheMap
    • Support postgres CacheMap
    • Support customization of the CacheMap(manually)
  • Common functions
    • I recommend gox, it provides ternary operator(If/IfFunc),Ptr,Pie,....

Decorator examples

Refer to: examples

Cache fibonacii function

Refer to: decorator fib example

Play: https://go.dev/play/p/7BCINKENJzA

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

Extension(pg)

Decorator config

Config item(gofnext.Config)

gofnext.Config item list:

Key Description Default
TTL Cache Time to Live 0(if TTL=0:use permanent cache; if TTL>0:set cache with TTL)
ErrTTL cache TTL for error return if there is an error 0(0:Donot cache error; >0:Cache error with TTL; -1:rely on TTL only; )
CacheMap Custom own cache Inner Memory
HashKeyPointerAddr Use Pointer Addr(&p) as key instead of its value when hashing key false(Use real value*p as key)
HashKeyFunc Custom hash key function Inner hash func
Cache's Live Time(TTL)

For example: set cache's live time to 1hour.

gofnext.CacheFn1Err(getUserScore, &gofnext.Config{
    /* Set cache's TTL time: 
        if TTL==0, use permanent cache; 
        if TTL>0, cache's live time is TTL
    */
    TTL:  time.Hour, 
}) 
Error Cache's Live Time(ErrTTl)

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

If there is an error, and you wanna control the error cache's TTL, simply add ErrTTL: time.Duration. Refer to: https://github.com/ahuigo/gofnext/blob/main/examples/decorator-err_test.go

gofnext.CacheFn1Err(getUserScore, &gofnext.Config{
    /* Set error cache's TTL time:
        if ErrTTL=0, do not cache error;
        if ErrTTL>0, error cache's live time is ErrTTL;
        if ErrTTL=-1, error cache's live time is controlled by TTL
    */
    ErrTTL: 0, // Do not cache error(default:0)
    ErrTTL: time.Seconds * 60, // error cache's live time is 60s
    ErrTTL: -1, // rely on TTL only
}) 
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 serializating for redis(#spec/reflect/unexported)

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(with error)

func CacheFn1

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

Cache Function with 1 parameter

func CacheFn1Err

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

Cache Function with 1 parameter(with error)

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(with error)

func CacheFn3 added in v0.0.22

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

Cache Function with 3 parameter

func CacheFn3Err added in v0.0.22

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

Cache Function with 3 parameter(with error)

func If added in v0.0.23

func If[T any](condition bool, trueVal, falseVal T) T

func NewCacheLru

func NewCacheLru(maxSize int) *cacheLru

func NewCacheRedis

func NewCacheRedis(funcKey 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
	SetErrTTL(ttl time.Duration) CacheMap
	NeedMarshal() bool
}

type Config

type Config struct {
	/* Set cache's TTL time:
	if TTL==0, use permanent cache;
	if TTL>0, cache's live time is TTL
	*/
	TTL time.Duration
	/* Set error cache's TTL time:
	if ErrTTL=0, do not cache error;
	if ErrTTL>0, error cache's live time is ErrTTL;
	if ErrTTL=-1, error cache's live time is controlled by TTL
	*/
	ErrTTL             time.Duration
	CacheMap           CacheMap
	NeedDumpKey        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