{"id":41441676,"url":"https://github.com/ahuigo/gofnext","last_synced_at":"2026-01-23T14:57:08.425Z","repository":{"id":209491797,"uuid":"720510587","full_name":"ahuigo/gofnext","owner":"ahuigo","description":"🛠️ Golang extended function like @cache decorator.","archived":false,"fork":false,"pushed_at":"2025-04-29T04:04:57.000Z","size":203,"stargazers_count":12,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-29T04:42:39.950Z","etag":null,"topics":["cache","decorator","functions","generic","go"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/ahuigo/gofnext","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ahuigo.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-11-18T17:53:47.000Z","updated_at":"2025-04-29T04:05:00.000Z","dependencies_parsed_at":"2023-12-18T10:52:41.310Z","dependency_job_id":"2da08c4a-c591-4957-9122-c5589c1936e1","html_url":"https://github.com/ahuigo/gofnext","commit_stats":null,"previous_names":["ahuigo/gocache-decorator"],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/ahuigo/gofnext","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahuigo%2Fgofnext","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahuigo%2Fgofnext/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahuigo%2Fgofnext/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahuigo%2Fgofnext/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ahuigo","download_url":"https://codeload.github.com/ahuigo/gofnext/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahuigo%2Fgofnext/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28694458,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-23T14:15:13.573Z","status":"ssl_error","status_checked_at":"2026-01-23T14:09:05.534Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","decorator","functions","generic","go"],"created_at":"2026-01-23T14:57:08.283Z","updated_at":"2026-01-23T14:57:08.418Z","avatar_url":"https://github.com/ahuigo.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🛠️ Go function extended\n[![tag](https://img.shields.io/github/tag/ahuigo/gofnext.svg)](https://github.com/ahuigo/gofnext/tags)\n![Go Version](https://img.shields.io/badge/Go-%3E%3D%201.21-%23007d9c)\n[![GoDoc](https://godoc.org/github.com/ahuigo/gofnext?status.svg)](https://pkg.go.dev/github.com/ahuigo/gofnext)\n![Build Status](https://github.com/ahuigo/gofnext/actions/workflows/test.yml/badge.svg)\n[![Go report](https://goreportcard.com/badge/github.com/ahuigo/gofnext)](https://goreportcard.com/report/github.com/ahuigo/gofnext)\n[![Coverage](https://img.shields.io/codecov/c/github/ahuigo/gofnext)](https://codecov.io/gh/ahuigo/gofnext)\n[![Contributors](https://img.shields.io/github/contributors/ahuigo/gofnext)](https://github.com/ahuigo/gofnext/graphs/contributors)\n[![License](https://img.shields.io/github/license/ahuigo/gofnext)](./LICENSE)\n\nThis **gofnext** provides the following functions extended(go\u003e=1.21).\n\nCache decorators(concurrent safe): Similar to Python's `functools.cache` and `functools.lru_cache`. \n\nIn addition to memory caching, it also supports Redis caching and custom caching.\n\n[DeepWiki](https://deepwiki.com/ahuigo/gofnext)\n[简体中文](/readme.zh.md)\n\n- [🛠️ Go function extended](#️-go-function-extended)\n  - [Decorator cases](#decorator-cases)\n  - [Features](#features)\n  - [Decorator examples](#decorator-examples)\n    - [Cache fibonacii function](#cache-fibonacii-function)\n    - [Cache function with 0 param](#cache-function-with-0-param)\n    - [Cache function with 1 param](#cache-function-with-1-param)\n    - [Cache function with 2 params](#cache-function-with-2-params)\n    - [Cache function with more params(\\\u003e2)](#cache-function-with-more-params2)\n    - [Cache function with lru cache](#cache-function-with-lru-cache)\n    - [Cache function with redis cache(unstable)](#cache-function-with-redis-cacheunstable)\n    - [Custom cache map](#custom-cache-map)\n    - [Extension(pg)](#extensionpg)\n  - [Decorator config](#decorator-config)\n    - [Config item(`gofnext.Config`)](#config-itemgofnextconfig)\n    - [Cache's Live Time(TTL)](#caches-live-timettl)\n    - [Error Cache's Live Time(ErrTTl)](#error-caches-live-timeerrttl)\n    - [Hash Pointer address or value?](#hash-pointer-address-or-value)\n    - [Custom hash key function](#custom-hash-key-function)\n  - [Roadmap](#roadmap)\n\n## Decorator cases\n\n| function        | decorator             |\n|-----------------|-----------------------|\n| func f() R    | gofnext.CacheFn0(f) |\n| func f(K1) R   | gofnext.CacheFn1(f) |\n| func f(K1,K2) R | gofnext.CacheFn2(f) |\n| func f(K1,K2,K3) R | gofnext.CacheFn3(f) |\n| func f() (R,error)    | gofnext.CacheFn0Err(f) |\n| func f(K1) (R,error)   | gofnext.CacheFn1Err(f)    |\n| func f(K1, K2) (R,error) | gofnext.CacheFn2Err(f)    |\n| func f() (R,error) | gofnext.CacheFn0Err(f, \u0026gofnext.Config{TTL: time.Hour})\u003cbr/\u003e// memory cache with ttl  |\n| func f() R | gofnext.CacheFn0(f, \u0026gofnext.Config{CacheMap: gofnext.NewCacheLru(9999)})  \u003cbr/\u003e// Maxsize of cache is 9999|\n| func f() R | gofnext.CacheFn0(f, \u0026gofnext.Config{CacheMap: gofnext.NewCacheRedis(\"cacheKey\")})  \u003cbr/\u003e// Warning: redis's marshaling may result in data loss|\n\n**Benchmark**\nBenchmark case: https://github.com/ahuigo/gofnext/blob/main/bench/\n```\n# golang1.22\npkg: github.com/ahuigo/gofnext/bench\nBenchmarkGetDataWithNoCache-10               100          11179015 ns/op          281220 B/op         99 allocs/op\nBenchmarkGetDataWithMemCache-10         11036955                95.49 ns/op           72 B/op          2 allocs/op\nBenchmarkGetDataWithLruCache-10         11362039               104.8 ns/op            72 B/op          2 allocs/op\nBenchmarkGetDataWithRedisCache-10          15850             74653 ns/op           28072 B/op         29 allocs/op\n```\n\n## Features\n- Cache Decorator (gofnext)\n    - [x] Decorator cache for function\n    - [x] Concurrent goroutine Safe\n    - [x] Support memory CacheMap(default)\n    - [x] Support memory-lru CacheMap\n    - [x] Support redis CacheMap\n    - [x] Support [postgres CacheMap](https://github.com/ahuigo/gofnext_pg)\n    - [x] Support customization of the CacheMap(manually)\n- Common functions\n    - I recommend [gox](https://github.com/icza/gox), it provides ternary operator(If/IfFunc),Ptr,Pie,....\n\n\n## Decorator examples\nRefer to: [examples](https://github.com/ahuigo/gofnext/blob/main/examples)\n\n### Cache fibonacii function\nRefer to: [decorator fib example](https://github.com/ahuigo/gofnext/blob/main/examples/decorator-fib_test.go)\n\u003e Play: https://go.dev/play/p/7BCINKENJzA\n\n```go\npackage main\nimport \"fmt\"\nimport \"github.com/ahuigo/gofnext\"\nfunc main() {\n    var fib func(int) int\n    fib = func(x int) int {\n        fmt.Printf(\"call arg:%d\\n\", x)\n        if x \u003c= 1 {\n            return x\n        } else {\n            return fib(x-1) + fib(x-2)\n        }\n    }\n    fib = gofnext.CacheFn1(fib)\n\n    fmt.Println(fib(5))\n    fmt.Println(fib(6))\n}\n```\n\n### Cache function with 0 param\nRefer to: [decorator example](https://github.com/ahuigo/gofnext/blob/main/examples/decorator_test.go)\n\n    package examples\n\n    import \"github.com/ahuigo/gofnext\"\n\n    func getUserAnonymouse() (UserInfo, error) {\n        fmt.Println(\"select * from db limit 1\", time.Now())\n        time.Sleep(10 * time.Millisecond)\n        return UserInfo{Name: \"Anonymous\", Age: 9}, errors.New(\"db error\")\n    }\n\n    var (\n        // Cacheable Function\n        getUserInfoFromDbWithCache = gofnext.CacheFn0Err(getUserAnonymouse) \n    )\n\n    func TestCacheFuncWithNoParam(t *testing.T) {\n        // Execute the function multi times in parallel.\n        times := 10\n        parallelCall(func() {\n            userinfo, err := getUserInfoFromDbWithCache()\n            fmt.Println(userinfo, err)\n        }, times)\n    }\n\n### Cache function with 1 param\nRefer to: [decorator example](https://github.com/ahuigo/gofnext/blob/main/examples/decorator-nil_test.go)\n\n    func getUserNoError(age int) (UserInfo) {\n    \ttime.Sleep(10 * time.Millisecond)\n    \treturn UserInfo{Name: \"Alex\", Age: age}\n    }\n    \n    var (\n    \t// Cacheable Function with 1 param and no error\n    \tgetUserInfoFromDbNil= gofnext.CacheFn1(getUserNoError) \n    )\n\n    func TestCacheFuncNil(t *testing.T) {\n    \t// Execute the function multi times in parallel.\n    \ttimes := 10\n    \tparallelCall(func() {\n    \t\tuserinfo := getUserInfoFromDbNil(20)\n    \t\tfmt.Println(userinfo)\n    \t}, times)\n    }\n\n### Cache function with 2 or 3 params\n\u003e Refer to: [decorator example](https://github.com/ahuigo/gofnext/blob/main/examples/decorator_test.go)\n\n    func TestCacheFuncWith2Param(t *testing.T) {\n        // Original function\n        executeCount := 0\n        getUserScore := func(c context.Context, id int) (int, error) {\n            executeCount++\n            fmt.Println(\"select score from db where id=\", id, time.Now())\n            time.Sleep(10 * time.Millisecond)\n            return 98 + id, errors.New(\"db error\")\n        }\n\n        // Cacheable Function\n        getUserScoreWithCache := gofnext.CacheFn2Err(getUserScore, \u0026gofnext.Config{\n            TTL: time.Hour,\n        }) // getFunc can only accept 2 parameter\n\n        // Execute the function multi times in parallel.\n        ctx := context.Background()\n        parallelCall(func() {\n            score, _ := getUserScoreWithCache(ctx, 1)\n            if score != 99 {\n                t.Errorf(\"score should be 99, but get %d\", score)\n            }\n            getUserScoreWithCache(ctx, 2)\n            getUserScoreWithCache(ctx, 3)\n        }, 10)\n\n        if executeCount != 3 {\n            t.Errorf(\"executeCount should be 3, but get %d\", executeCount)\n        }\n    }\n\n### Cache method\nOriginal method:\n\n    type User Struct{}\n    func NewUser() (*User){\n        u := \u0026User{}\n        return u\n    }\n\n    func (u *User) getUserInfo(uid int) (*User, error){  \n        ...  \n    }\n\nCached method(singleton mode):\n\n```\ntype User struct {}\nvar (\n    getUserInfoCached func(uid int) (*User, error)\n    once             sync.Once\n)\n\nfunc NewUser() *User {\n    u := \u0026User{}\n    once.Do(func() {\n        // use cache decorator\n        getUserInfoCached = gofnext.CacheFn1Err(u.getUserInfo)\n    })   \n    return u\n}\n\n// Cached method\nfunc (u *User) GetUserInfo(uid int) (*User, error) {\n    return getUserInfoCached(uid)\n}\n\n// Origin method\nfunc (u *User) getUserInfo(uid int) (*User, error) {\n    fmt.Printf(\"mock data: uid=%d\\n\", uid)\n    return \u0026User{}, nil\n}\n```\n\nCached method(factory mode):\n\n    type User Struct{\n        getUserInfoCached func(uid int) (\u0026User, error)\n    }\n    func NewUser() *User{\n        u := \u0026User{}\n       \tu.getUserInfoCached = gofnext.CacheFn1Err(u.getUserInfo) \n        return u\n    }\n    ....\n\n### Cache function with more params(\u003e3)\nRefer to: [decorator example](https://github.com/ahuigo/gofnext/blob/main/examples/decorator_test.go)\n\n\texecuteCount := 0\n\ttype Stu struct {\n\t\tname   string\n\t\tage    int\n\t\tgender int\n\t}\n\n\t// Original function\n\tfn := func(name string, age, gender int) int {\n\t\texecuteCount++\n\t\t// select score from db where name=name and age=age and gender=gender\n\t\tswitch name {\n\t\tcase \"Alex\":\n\t\t\treturn 10\n\t\tdefault:\n\t\t\treturn 30\n\t\t}\n\t}\n\n\t// Convert to extra parameters to a single parameter(2 prameters is ok)\n\tfnWrap := func(arg Stu) int {\n\t\treturn fn(arg.name, arg.age, arg.gender)\n\t}\n\n\t// Cacheable Function\n\tfnCachedInner := gofnext.CacheFn1(fnWrap)\n\tfnCached := func(name string, age, gender int) int {\n\t\treturn fnCachedInner(Stu{name, age, gender})\n\t}\n\n\t// Execute the function multi times in parallel.\n\tparallelCall(func() {\n\t\tscore := fnCached(\"Alex\", 20, 1)\n\t\tif score != 10 {\n\t\t\tt.Errorf(\"score should be 10, but get %d\", score)\n\t\t}\n\t\tfnCached(\"Jhon\", 21, 0)\n\t\tfnCached(\"Alex\", 20, 1)\n\t}, 10)\n\n    // Test Count\n    if executeCount != 2 {\n\t\tt.Errorf(\"executeCount should be 2, but get %d\", executeCount)\n\t}\n\n### Cache function with lru cache\nRefer to: [decorator lru example](https://github.com/ahuigo/gofnext/blob/main/examples/decorator-lru_test.go)\n\n\texecuteCount := 0\n\tmaxCacheSize := 2\n\tvar getUserScore = func(more int) (int, error) {\n\t\texecuteCount++\n\t\treturn 98 + more, errors.New(\"db error\")\n\t}\n\n\t// Cacheable Function\n\tvar getUserScoreFromDbWithLruCache = gofnext.CacheFn1Err(getUserScore, \u0026gofnext.Config{\n\t\tTTL:      time.Hour,\n\t\tCacheMap: gofnext.NewCacheLru(maxCacheSize),\n\t})\n\n### Cache function with redis cache(unstable)\n\u003e Warning: Since redis needs JSON marshaling, this may result in data loss.\n\nRefer to: [decorator redis example](https://github.com/ahuigo/gofnext/blob/main/examples/decorator-redis_test.go)\n\n    var (\n        // Cacheable Function\n        getUserScoreWithCache = gofnext.CacheFn1Err(getUserScore, \u0026gofnext.Config{\n            TTL:  time.Hour,\n            CacheMap: gofnext.NewCacheRedis(\"redis-cache-key\"),\n        }) \n    )\n\n    func TestRedisCacheFuncWithTTL(t *testing.T) {\n        // Execute the function multi times in parallel.\n        for i := 0; i \u003c 10; i++ {\n            score, _ := getUserScoreWithCache(1)\n            if score != 99 {\n                t.Errorf(\"score should be 99, but get %d\", score)\n            }\n        }\n    }\n\nTo avoid keys being too long, you can limit the length of Redis key:\n\n    cacheMap := gofnext.NewCacheRedis(\"redis-cache-key\").SetMaxHashKeyLen(256);\n\nSet redis config:\n\n\t// method 1: by default: localhost:6379\n\tcache := gofnext.NewCacheRedis(\"redis-cache-key\") \n\n\t// method 2: set redis addr\n\tcache.SetRedisAddr(\"192.168.1.1:6379\")\n\n\t// method 3: set redis options\n\tcache.SetRedisOpts(\u0026redis.Options{\n\t\tAddr: \"localhost:6379\",\n\t})\n\n\t// method 4: set redis universal options\n\tcache.SetRedisUniversalOpts(\u0026redis.UniversalOptions{\n\t\tAddrs: []string{\"localhost:6379\"},\n\t})\n\n### Custom cache map\nRefer to: https://github.com/ahuigo/gofnext/blob/main/cache-map-mem.go\n\n### Extension(pg)\n- Postgres cache extension: https://github.com/ahuigo/gofnext_pg\n\n## Decorator config\n### Config item(`gofnext.Config`)\ngofnext.Config item list:\n\n| Key | Description      |      Default       |\n|-----|------------------|--------------------|\n| TTL    | Cache Time to Live |0(if TTL=0:use permanent cache; if TTL\u003e0:set cache with TTL)  |\n| ErrTTL | cache TTL for error return if there is an error | 0(0:Donot cache error;  \u003e0:Cache error with TTL; -1:rely on TTL only; )  |\n| CacheMap|Custom own cache   | Inner Memory  |\n| HashKeyPointerAddr | Use Pointer Addr(\u0026p) as key instead of its value when hashing key |false(Use real value`*p` as key) |\n| HashKeyFunc| Custom hash key function | Inner hash func|\n\n### Cache's Live Time(TTL)\nFor example: set cache's live time to 1hour.\n\n    gofnext.CacheFn1Err(getUserScore, \u0026gofnext.Config{\n        /* Set cache's TTL time: \n            if TTL==0, use permanent cache; \n            if TTL\u003e0, cache's live time is TTL\n        */\n        TTL:  time.Hour, \n    }) \n\n### Error Cache's Live Time(ErrTTl)\n\u003e By default, gofnext won't cache error when there is an error.\n\nIf there is an **error**, and you wanna control the error cache's TTL, simply add `ErrTTL: time.Duration`.\nRefer to: https://github.com/ahuigo/gofnext/blob/main/examples/decorator-err_test.go\n\n    gofnext.CacheFn1Err(getUserScore, \u0026gofnext.Config{\n        /* Set error cache's TTL time:\n            if ErrTTL\u003c=0, do not cache error;\n            if ErrTTL\u003e0, error cache's live time is ErrTTL;\n        */\n        ErrTTL: 0, // Do not cache error(default:0)\n        ErrTTL: time.Seconds * 60, // error cache's live time is 60s\n    }) \n\n### Hash Pointer address or value?\nDecorator will hash function's all parameters into hashkey.\nBy default, if parameter is pointer, decorator will hash its real value instead of pointer address.\n\nIf you wanna hash pointer address, you should turn on `HashKeyPointerAddr`:\n\n\tgetUserScoreWithCache := gofnext.CacheFn1Err(getUserScore, \u0026gofnext.Config{\n\t\tHashKeyPointerAddr: true,\n\t})\n\n### Custom hash key function\n\u003e In this case, you need to ensure that duplicate keys are not generated.\nRefer to: [example](https://github.com/ahuigo/gofnext/blob/main/examples/decorator-key-custom_test.go)\n\n\t// hash key function\n\thashKeyFunc := func(keys ...any) []byte{\n\t\tuser := keys[0].(*UserInfo)\n\t\tflag := keys[1].(bool)\n\t\treturn []byte(fmt.Sprintf(\"user:%d,flag:%t\", user.id, flag))\n\t}\n\n\t// Cacheable Function\n\tgetUserScoreWithCache := gofnext.CacheFn2Err(getUserScore, \u0026gofnext.Config{\n\t\tHashKeyFunc: hashKeyFunc,\n\t})\n\n## Roadmap\n- [] Include private property when serializating for redis(#spec/reflect/unexported)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fahuigo%2Fgofnext","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fahuigo%2Fgofnext","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fahuigo%2Fgofnext/lists"}