{"id":24535330,"url":"https://github.com/chippyash/go-cache-manager","last_synced_at":"2026-02-13T23:32:11.680Z","repository":{"id":272276346,"uuid":"916031893","full_name":"chippyash/go-cache-manager","owner":"chippyash","description":"Provides a simplified interface to key/value cache management utilising adaptors to provide the same functionality for different cache databases.","archived":false,"fork":false,"pushed_at":"2025-01-21T07:34:58.000Z","size":108,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-14T23:09:59.214Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/chippyash.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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}},"created_at":"2025-01-13T10:25:17.000Z","updated_at":"2025-01-21T07:34:15.000Z","dependencies_parsed_at":"2025-01-13T12:28:58.713Z","dependency_job_id":"2673eb1b-c34c-4a01-87a4-a9ff992ed1d5","html_url":"https://github.com/chippyash/go-cache-manager","commit_stats":null,"previous_names":["chippyash/go-cache-manager"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chippyash%2Fgo-cache-manager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chippyash%2Fgo-cache-manager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chippyash%2Fgo-cache-manager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chippyash%2Fgo-cache-manager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chippyash","download_url":"https://codeload.github.com/chippyash/go-cache-manager/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248975316,"owners_count":21192210,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":[],"created_at":"2025-01-22T12:17:28.065Z","updated_at":"2026-02-13T23:32:11.669Z","avatar_url":"https://github.com/chippyash.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Multi Adapter Cache Manager\n## github.com/chippyash/go-cache-manager\n\n## What\n\nProvides a simplified interface to key/value cache management utilising adaptors to provide the same functionality for\ndifferent cache databases.\n\nBased around some ideas that come from my PHP days and particularly the Zend Framework (now Laminas) that had a fully \nfunctional simple cache manager. See https://docs.laminas.dev/laminas-cache/\n\nThis will never try to be a friend to all. For straightforward caching operations, it should work.\n\n### Design considerations\n\n - Given the Go language, be as simple as possible\n - Deal with straight forward key/value caching\n - Make each cache backend look the same, so they can be swapped in and out\n - Allow devs to use the native client if they need to \n\n### Current backends\n\n - Memory\n - Valkey/Redis\n - S3 Bucket\n\n## How\n\nGo V1.23.4+\n\n**Please consider any version of this library at \u003c 1 as pre production**. Use at your own risk. But please do try it out. The more feedback \nI get, the better it will be.  That said, I am using it in production.\n\n## For Production\n\n```go\nimport \"github.com/chippyash/go-cache-manager/adapter/valkey\"\nimport \"github.com/chippyash/go-cache-manager/adapter/memory\"\nimport \"github.com/chippyash/go-cache-manager/storage\"\n```\n\n### Memory Cache\n\n```go\nns := \"myNameSpace:\"  //this can be left blank\nttl := time.Minute * 5 //Expiry Ttl\npurgeTtl := time.Minute * 10  //set the purgeTtl \u003e ttl\ncacheManager := memory.New(ns, ttl, purgeTtl)\n```\n\nThe underlying client for the Memory Cache is [github.com/patrickmn/go-cache](https://github.com/patrickmn/go-cache)\n\nUsing the setter methods for the Memory cache adapter allows you pass any type of value. The value will be stored as-is\nin memory and returned via any of the getter methods as type interface{} (any). You are responsible for casting it to your \nrequired type;\n\n```go\ncacheManager := memory.New(ns, ttl, purgeTtl)\nok, err := cacheManager.SetItem(\"key\", someValue)\nif !ok || err != nil {\n\tpanic(errors.Wrap(err, \"could not set value for key\"))\n}\nv, err := cacheManager.GetItem(\"key\")\nif err != nil {\n\tpanic(err)\n}\nsomeValue := v.(uint64)\n```\n### Valkey (Redis) Cache\n\n```go\nns := \"myNameSpace:\"  //this can be left blank\nhost := \"127.0.0.1\"\nttl := time.Minute * 5 //Expiry Ttl\nclientCaching := true //we want to use client side caching\nclientCachingTtl = time.Minute * 4 //set this to less than the ttl\ncacheManager, err := valkey.New(ns, host, ttl, clientCaching, clientCachingTtl, false).Open()\nif err != nil {\n\tpanic(err)\n}\n```\n\nThe underlying client for the Valkey Cache is [github.com/valkey-io/valkey-go](github.com/valkey-io/valkey-go)\n\nUsing the setter methods for the Valkey cache adapter allows you pass any type of value. The value will be stored as a\nstring in the cache server and returned via any of the getter methods as type **interface{}|string**. You are responsible for casting it \nto your required type.\n\n```go\ncacheManager := valkey.New(ns, host, ttl, clientCaching, clientCachingTtl, false).Open()\nok, err := cacheManager.SetItem(\"key\", someValue)\nif !ok || err != nil {\n\tpanic(errors.Wrap(err, \"could not set value for key\"))\n}\nv, err := cacheManager.GetItem(\"key\")\nif err != nil {\n\tpanic(err)\n}\nsomeValue, err := strconv.ParseUint(v.(string), 10, 64)\nif err != nil {\n    panic(err)\n}\n//note that for other types, you may need a second cast\nu64, err := strconv.ParseUint(v.(string), 10, 32)\nif err != nil {\n    panic(err)\n}\nsomeValue = uint32(u64)\n```\n\n\nAs this can get tiresome if you have to do a lot of conversion, you can switch on data management by setting the 'manageTypes'\nparameter for valkey.New() (the last parameter) to true.  When getting cache items, they will be returned as the same type \nin which they were set.\n\nThis is achieved by recording the item's data type in the cache in the 'gcm' namespace. Of course, having to hit the cache\ntwice, means that performance will be marginally impacted but you might consider this an acceptable cost in return for easier\ndata handling. It also means that the Valkey/Redis adapter works in the same way as the Memory adapter making swapping \none for another a breeze.\n\n```go\ncacheManager := valkey.New(ns, host, ttl, clientCaching, clientCachingTtl, true).Open()\nsomeValue := []byte(\"foobar\")\nok, err := cacheManager.SetItem(\"key\", someValue)\nif !ok || err != nil {\n\tpanic(errors.Wrap(err, \"could not set value for key\"))\n}\nv, err := cacheManager.GetItem(\"key\")\nif err != nil {\n\tpanic(err)\n}\nfetchedValue := v.([]byte)\n//true == bytes.Equal(someValue, fetchedValue)\n```\n\nFor time.Time values to work correctly, we need to know the formatting string to use. This is set in the adapter\noptions (see 'Setting options' below). You need to set the `valkey.OptDatetimeFormat` to your required format. It is set to\n`time.RFC3339` by default.\n\n### S3 Bucket\nThis adapter is provided as a working example of how you can back your cache with an S3 bucket.  S3 provides cheap, but by\ncaching standards, slow storage. There are circumstances however that dictate that you want to have primary data stored in\nS3, most usually in JSON or CSV format and use this as a source to feed more performant caches. As such, I wouldn't expect\nthis adapter to be the primary adapter, but chained behind another adapter. Hopefully, this adapter will guide you to \nroll your own file/object backing caches.\n\n```go\nimport \"github.com/chippyash/go-cache-manager/adapter/bucket\"\nimport \"github.com/aws/aws-sdk-go-v2/service/s3\"\n\nbucket := \"my-cache-bucket\"\nprefix := \"some/folder/\"\nsuffix := \".json\"\nmimeType := bucket.MimeTypeJson\nregion := \"eu-west-2\"\ncacheManager, err := bucket.New(bucket, prefix, suffix, mimeType, region)\nif err != nil {\n\tpanic(err)\n}\n```\n\nIt is limited in its functional ability:\n\n - It is only able to deal with `string` and `[]byte` input data types. Any other data type will produce an error.\n - It's functionality is limited as some methods don't make sense. Only the following methods are supported:\n   - GetItem\n   - GetItems\n   - SetItem\n   - SetItems\n   - HasItem\n   - HasItems\n   - RemoveItem\n   - RemoveItems\n   - Open - No Op\n   - Close - No Op\n - The Getter methods always return a string value\n\nThe `mimeType` parameter for the constructor is used by s3 to indicate the object type, so that it is properly parsed by other\nS3 operations.\n\nTo unmarshal the value use something like:\n\n```go\nval, err := cacheManager.GetItem(\"key\")\nif err != nil {\n\tpanic(err)\n}\nvar obj MyObject\nerr := json.Unmarshal([]byte(val.(string)), \u0026obj)\n```\n\nThe most common usage for this type of cache is to store blobs of data, most commonly in JSON or CSV. (NB, I'm looking at \nextending this to Parquet and other formats).  For instance, in my shop, we have systems producing and updating some configuration\nvalues (expressed as JSON) into an S3 bucket. The production application uses a Redis cache, behind a memory cache to store \nthat config for fast access. But in the event of cache failure, it looks back to the S3 bucket as its source of truth.\n\n![docs/nested-cache.puml](docs/nested-cache.png)\n\nThe underlying client for this adapter is the [AWS Go SDK V2 S3 Client](\"github.com/aws/aws-sdk-go-v2/service/s3\"). You \nare encouraged to RTFM on S3. Most problems using this adapter will be because you did not RTFM!\n\nAuthentication for the client uses the environment method based on environment variables etc.  See [AWS Config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config)\nfor additional information.  If you need support for other methods, please consider a Pull Request.\n\n### Namespaces\nEach adapter allows you to declare a namespace. This is simply prefixed to any key value that you use. Thus, you can create multiple\ncache adapters in your application and be certain that their entries are separated out in your cache backend.\n\nTraditionally in Redis we split out cache names using the ':' character. This is recognised by many Redis clients and is \nused to create a tree hierarchy display of cache keys. To achieve the same set the namespace name as '\\\u003cname\u003e:', e.g. 'categories:'.\n\n### Chaining adapters\nThe library supports chaining adapters together.\n\n```go\ncacheManager := memory.New(ns, ttl, purgeTtl)\nchainedAdapter, err := := valkey.New(ns, host, ttl, false, time.Second * 0, false).Open()\ncacheManager.(storage.Chainable).ChainAdapter(chainedAdapter)\n```\n\nThis is a solution that allows you to put a memory cache in front of a Valkey/Redis cache.  Note however, that if your\nValkey/Redis server supports client side caching, you can simply use the previous example for 'Valkey (Redis) Cache'\n\n### Adapter Methods\nFor a full list of available adapter methods (functions) see [the Storage interface](storage/storageinterface.go)\n\n#### The Open and Close methods\nSome adapters will try to make an immediate connection to the data source when they are constructed. The Valkey adapter\nis one such adapter. For this reason we delay the connection until the Open() method is called. This allows you to construct\nthe adapter in advance, maybe in your DIC, and then open it when you actually need it.  The memory adapter doesn't need you to \ncall Open, but as a matter of course, you should do as this allows for greater interchangeability between adapters.\n\nSimilarly, although no functionality currently exists in the Close method for the provided adapters, you should get into\nthe habit of deferring a call to it.\n\n```go\nadapter, err := valkey.New(ns, host, ttl, false, time.Second * 0, false).Open()\nif err != nil {\n\tpanic(err)\n}\ndefer(adapter.Close())\n//or more properly\ndefer(func(){\n\tif err := adapter.Close(); err != nil {\n        panic(err)\n    }\n})\n```\n\n### Setting options\nEach adapter is set with a sane set of options.  You can reconfigure the options just after instantiating the adapter\nby calling `adapter.SetOptions(opts)`. opts is a storage.StorageOptions object. Note that each adapter will have a default\nset of options, but may have additional options specific to the adapter.\n\n```go\ncache := valkey.New(ns, host, ttl, false, time.Second * 0, false)\nopts := cache.GetOptions()\n//do something with the options\n// ...\ncache.SetOptions(opts)\ncache, err := cache.Open()\n```\n\nNote that the options are untyped. You need to type them correctly in order to use them.\n\n```go\nimport vk \"github.com/chippyash/go-cache-manager/adapter/valkey\"\nimport \"github.com/valkey-io/valkey-go\"\n\nopts := cache.GetOptions()\nvalkeyOpts := opts[vk.OptValkeyOptions].(valkey.ClientOption)\n//set up cluster connection\nvalkeyOpts.InitAddress = []string{\"127.0.0.1:7001\", \"127.0.0.1:7002\", \"127.0.0.1:7003\"}\nvalkeyOpts.ShuffleInit = true\nopts[vk.OptValkeyOptions] = valkeyOpts\ncache.SetOptions(opts)\ncache, err := cache.Open()\n```\n\n### Using the underlying client\nIn some circumstances, this library may not give exactly what you want. In that case you can retrieve the underlying client\nand act upon your cache backend more directly.\n\n#### Memory Cache Client\n```go\nimport \"github.com/chippyash/go-cache-manager/adapter\"\nimport \"github.com/chippyash/go-cache-manager/adapter/memory\"\nimport \"github.com/patrickmn/go-cache\"\n\ncacheManager := memory.New(ns, ttl, purgeTtl)\nclient := cacheManager.(*adapter.AbstractAdapter).Client.(*cache.Cache)\n```\n\n#### Valkey Cache Client\n```go\nimport \"github.com/chippyash/go-cache-manager/adapter\"\nimport vk \"github.com/chippyash/go-cache-manager/adapter/valkey\"\nimport \"github.com/valkey-io/valkey-go\"\n\ncacheManager, err := vk.New(ns, host, ttl, clientCaching, clientCachingTtl, false).Open()\nclient := cacheManager.(*adapter.AbstractAdapter).Client.(valkey.Client)\n```\n\n#### S3 Bucket Client\n```go\nimport \"github.com/chippyash/go-cache-manager/adapter\"\nimport \"github.com/chippyash/go-cache-manager/adapter/bucket\"\nimport \"github.com/aws/aws-sdk-go-v2/service/s3\"\n\ncacheManager, err := bucket.New(bucket, prefix, suffix, mimeType, region)\nclient := cacheManager.(*adapter.AbstractAdapter).Client.(*s3.Client)\n```\n\n## For Development\n\nIf you want to add another adapter, you should carefully study the three so far provided.  Write your code, including the unit\ntests, which as a simple base should mimic what are already done, plus any required by your specific adapter.\n\nThis lib is peculiar in that it follows a well trodden path, in the PHP world,  of using an Abstract parent to all adapters \nand then decorating the concrete objects with the functionality to carry out the interface methods.  Make sure you understand that. In effect \nan adapter is just a bunch of functions that are set when the adapter is constructed.  This makes it easy for you to:\n\n - Create an adapter on the fly\n - Amend an existing adapter to your specific requirements\n#### Create an adapter on the fly\n```go\nimport (\n    \"github.com/chippyash/go-cache-manager/adapter\"\n    \"github.com/chippyash/go-cache-manager/storage\"\n)\n\nadapter := new(adapter.AbstractAdapter)\n//set the name\nadapter.Name = \"myadapter\"\n//set the client\nadapter.Client = some.Client\n//create minimal options and add them\nopts := storage.StorageOptions{\n    storage.OptNamespace:      \"\",\n    storage.OptReadable:       true,\n    storage.OptWritable:       true,\n    storage.OptDataTypes:      storage.DefaultDataTypes,\n}\nadapter.SetOptions(opts)\n//set just the functions you are actually going to use\nadapter.\n    SetGetItemFunc(func(key string) (any, error) { //your code here }).\n    SetSetItemFunc(func(key string, value any) (bool, error) { //your code here })\n//and use your adapter\na, _ := adapter.GetItem(\"key\")\nok, _ := adapter.SetItem(\"anotherkey\", \"value\")\n```\n\n#### Amend an existing adapter\nLet's say, for example, that you don't quite like the way that the Valkey adapter handles a particular method, it doesn't\nquite meet your requirements or that you have found a bug in the method, and you want to temporarily patch it until your pull \nrequest is merged.  Construct the adapter as normal, and then override the offending function:\n\n```go\nimport (\n    \"github.com/chippyash/go-cache-manager/adapter\"\n    \"github.com/chippyash/go-cache-manager/adapter/valkey\"\n)\n\nadapter, _ := valkey.New(ns, host, ttl, clientCaching, clientCachingTtl, false).Open()\nadapter.(adapter.AbstractAdapter).SetHasItemFunc(func(key string) bool { //your code here })\n//then use the adapter as normal\n```\n\n### Changing this library\nChanges to the existing interface will not be accepted without a long discussion because they may cause a BC break.\n\nAdditions to the interface can be accepted, as long as you make the changes to all the currently supported concrete \nimplementations.\n\nUpdates to the documentation i.e. this README.md, are most welcome. I speak in UK English. That may not always make sense \nto our friends around the world, so if you have better ways of phrasing or want to provide another language README, then\nplease contact me, directly or via an issue in this repo.\n\nAs normal, fork the library, make your changes and request a pull request back into this repo. Put your changes on a branch.\n\n### Unit Testing\n`make test`\n\nUnit tests for the Valkey adapter run against [miniredis](github.com/alicebob/miniredis/v2) which does not offer support\nfor client side caching. Thus, this piece of functionality is currently has no unit test, so be aware!  In due course, I'll get \nthe test suite running against a real Valkey server. I do have it running against a production [Valkey](https://valkey.io/) \nserver that supports client side caching and so far, no problems. It works out of the box. \n\nUnit tests for the S3 bucket run against a mock S3 client, so use with caution. As above, once I get this lib running against\na real S3 bucket via a third party test runner, you'll have more confidence.\n\n## License\nThis software is released under the MIT License. See LICENSE.txt for details.\n\nFor license information of dependencies, please see licenses.csv.  If the dependencies change run `make license-check`\nto update the file.\n\n## Some thanks\n - Thanks to the original Zend team that came up with Zend/Cache, some of whom I know, but they shall be nameless. They\ngave many hours of sport back in the day!\n - Thanks to [@rueian](https://github.com/rueian) for his very fast support on Valkey. He is a main contributor to Valkey.\nThanks to him the initial build of this lib (memory, valkey) took 2.5 days to write instead of 2.5 weeks.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchippyash%2Fgo-cache-manager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchippyash%2Fgo-cache-manager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchippyash%2Fgo-cache-manager/lists"}