Thanks to visit codestin.com
Credit goes to github.com

Skip to content

πŸš€ Schema based, typed Redis caching/memoize framework for Go

License

Yiling-J/cacheme-go

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

53 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

cacheme - Redis Caching Framework For Go

example workflow Go Report Card

English | δΈ­ζ–‡

  • Statically Typed - 100% statically typed using code generation.
  • Scale Efficiently - thundering herd protection via pub/sub.
  • Cluster Support - same API for redis & redis cluster.
  • Memoize - dynamic key generation based on code generation.
  • Versioning - cache versioning for better management.
  • Pipeline - reduce io cost by redis pipeline.

Read this first: Caches, Promises and Locks. This is how caching part works in cacheme.

Installation

go get github.com/Yiling-J/cacheme-go/cmd

After installing cacheme-go codegen, go to the root directory of your project, and run:

go run github.com/Yiling-J/cacheme-go/cmd init

The command above will generate cacheme directory under root directory:

└── cacheme
    β”œβ”€β”€ fetcher
 Β Β  β”‚Β Β  └── fetcher.go
    └── schema
        └── schema.go

Add Schema

Edit schema.go and add some schemas:

package schema

import (
	"time"

	cacheme "github.com/Yiling-J/cacheme-go"
)

var (
	// default prefix for redis keys
	Prefix = "cacheme"

	// extra imports in generated file
	Imports = []string{}

	// store schemas
	Stores = []*cacheme.StoreSchema{
		{
			Name:    "Simple",
			Key:     "simple:{{.ID}}",
			To:      "string",
			Version: 1,
			TTL:     5 * time.Minute,
		},
	}
)

More details here

Store Generation

Run code generation from the root directory of the project as follows:

go run github.com/Yiling-J/cacheme-go/cmd generate

This produces the following files:

└── cacheme
 Β Β  β”œβ”€β”€ fetcher
 Β Β  β”‚Β Β  └── fetcher.go
    β”œβ”€β”€ schema
    β”‚Β Β  └── schema.go
    └── store.go

store.go is generated based on schemas in schema.go. Adding more schemas and run generate again.

Add Fetcher

Each cache store should provide a fetch function in fetcher.go:

func Setup() {
	cacheme.SimpleCacheStore.Fetch = func(ctx context.Context, ID string) (string, error) {
		return ID, nil
	}
}

Use Your Stores

Create a client and get data, fetch function will be called if cache not exist:

import (
	"your_project/cacheme"
	"your_project/cacheme/fetcher"
)

func example() (string, error) {
	ctx := context.TODO()
	fetcher.Setup()
	client := cacheme.New(
		redis.NewClient(&redis.Options{
			Addr:     "localhost:6379",
			Password: "",
			DB:       0,
		}),
	)
	store := client.SimpleCacheStore
	result, err := store.Get(ctx, "foo")
	if err != nil {
		return "", err
	}
	return result, err
}

Redis Cluster:

import (
	"your_project/cacheme"
	"your_project/cacheme/fetcher"
)

func example() (string, error) {
	ctx := context.TODO()
	fetcher.Setup()
	client := cacheme.NewCluster(
		redis.NewClusterClient(&redis.ClusterOptions{
			Addrs: []string{
				":7000",
				":7001",
				":7002"},
		}),
	)
	store := client.SimpleCacheStore
	result, err := store.Get(ctx, "foo")
	if err != nil {
		return "", err
	}
	return result, err
}

Invalid your cache:

err := store.Invalid(ctx, "foo")

Update your cache:

err := store.Update(ctx, "foo")

Redis pipeline(using same client, skip client creation here):

import cachemego "github.com/Yiling-J/cacheme-go"

...
pipeline := cachemego.NewPipeline(client.Redis())
ids := []string{"1", "2", "3", "4"}
var ps []*cacheme.SimplePromise
for _, i := range ids {
	promise, err := client.SimpleCacheStore.GetP(ctx, pipeline, i)
	ps = append(ps, promise)
}
err = pipeline.Execute(ctx)
fmt.Println(err)

for _, promise := range ps {
	r, err := promise.Result()
	fmt.Println(r, err)
}

Mixed pipeline:

import cachemego "github.com/Yiling-J/cacheme-go"

...
// same pipeline for different stores
pipeline := cachemego.NewPipeline(client.Redis())

ids := []string{"1", "2", "3", "4"}
var ps []*cacheme.SimplePromise // cache string
var psf []*cacheme.FooPromise // cache model.Foo struct
for _, i := range ids {
	promise, err := client.SimpleCacheStore.GetP(ctx, pipeline, i)
	ps = append(ps, promise)
}
for _, i := range ids {
	promise, err := client.FooCacheStore.GetP(ctx, pipeline, i)
	psf = append(psf, promise)
}
// execute only once
err = pipeline.Execute(ctx)
fmt.Println(err)
// simple store results
for _, promise := range ps {
	r, err := promise.Result()
	fmt.Println(r, err)
}
// foo store results
for _, promise := range psf {
	r, err := promise.Result()
	fmt.Println(r, err)
}

Invalid all cache with version:

// invalid all version 1 simple cache
client.SimpleCacheStore.InvalidAll(ctx, 1)

Schema Definition

Each schema has 5 fields:

  • Name - store name, will be struct name in generated code, capital first.
  • Key - key with variable using go template syntax, Variable name will be used in code generation.
  • To - cached value type string, will be used in code generation. Examples:
    • string: "string"
    • struct: "model.Foo"
    • struct pointer: "*model.Foo"
    • slice: "[]model.Foo"
  • Version - version number, for schema change.
  • TTL - redis ttl using go time.

Notes:

  • Duplicate name/key is not allowed.

  • Full redis key has 3 parts: prefix + schema key + version. Schema Keycategory:{{.categoryID}}:book:{{.bookID}} with prefix cacheme, version 1 will generate key:

     cacheme:category:1:book:3:v1
    

    Also you will see categoryID and bookID in generated code, as fetch func params.

  • In schema.go, there is an imports part:

     Imports = []string{}

    If you use structs in To, don't forget to add struct path here, so code generation can work:

     // we have model.Foo struct in schema
     Imports = []string{"your_project/model"}

About

πŸš€ Schema based, typed Redis caching/memoize framework for Go

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages