Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat: allow store options override in Set() method
  • Loading branch information
eko committed Oct 15, 2019
1 parent 0d1032b commit d28a9c2
Show file tree
Hide file tree
Showing 35 changed files with 313 additions and 233 deletions.
11 changes: 7 additions & 4 deletions Makefile
@@ -1,12 +1,15 @@
.PHONY: mocks

mocks:
# mocks
mockery -case=snake -name=CacheInterface -dir=cache/ -output test/mocks/cache/
mockery -case=snake -name=CodecInterface -dir=codec/ -output test/mocks/codec/
mockery -case=snake -name=SetterCacheInterface -dir=cache/ -output test/mocks/cache/
mockery -case=snake -name=MetricsInterface -dir=metrics/ -output test/mocks/metrics/
mockery -case=snake -name=StoreInterface -dir=store/ -output test/mocks/store/
mockery -case=snake -name=BigcacheClientInterface -dir=store/ -output test/mocks/store/
mockery -case=snake -name=MemcacheClientInterface -dir=store/ -output test/mocks/store/
mockery -case=snake -name=RedisClientInterface -dir=store/ -output test/mocks/store/
mockery -case=snake -name=RistrettoClientInterface -dir=store/ -output test/mocks/store/

# in package store clients mocks
mockery -case=snake -inpkg -name=BigcacheClientInterface -dir=store/ -output store/
mockery -case=snake -inpkg -name=MemcacheClientInterface -dir=store/ -output store/
mockery -case=snake -inpkg -name=RedisClientInterface -dir=store/ -output store/
mockery -case=snake -inpkg -name=RistrettoClientInterface -dir=store/ -output store/
77 changes: 47 additions & 30 deletions README.md
Expand Up @@ -16,6 +16,7 @@ Here is what it brings in detail:
* ✅ A loadable cache: allow you to call a callback function to put your data back in cache
* ✅ A metric cache to let you store metrics about your caches usage (hits, miss, set success, set error, ...)
* ✅ A marshaler to automatically marshal/unmarshal your cache values as a struct
* ✅ Define default values in stores and override them when setting data

## Built-in stores

Expand All @@ -40,10 +41,15 @@ Here is a simple cache instanciation with Redis but you can also look at other a
```go
memcacheStore := store.NewMemcache(
memcache.New("10.0.0.1:11211", "10.0.0.2:11211", "10.0.0.3:11212"),
&store.Options{
Expiration: 10*time.Second,
},
)

cacheManager := cache.New(memcacheStore, &cache.Options{Expiration: 15*time.Second})
err := cacheManager.Set("my-key", []byte("my-value))
cacheManager := cache.New(memcacheStore)
err := cacheManager.Set("my-key", []byte("my-value), &cache.Options{
Expiration: 15*time.Second, // Override default value of 10 seconds defined in the store
})
if err != nil {
panic(err)
}
Expand All @@ -55,12 +61,10 @@ value := cacheManager.Get("my-key")
```go
bigcacheClient, _ := bigcache.NewBigCache(bigcache.DefaultConfig(5 * time.Minute))
bigcacheStore := store.NewBigcache(
bigcacheClient,
)
bigcacheStore := store.NewBigcache(bigcacheClient, nil) // No otions provided (as second argument)
cacheManager := cache.New(bigcacheStore, nil)
err := cacheManager.Set("my-key", "my-value")
cacheManager := cache.New(bigcacheStore)
err := cacheManager.Set("my-key", "my-value", nil)
if err != nil {
panic(err)
}
Expand All @@ -71,14 +75,18 @@ value := cacheManager.Get("my-key")
#### Memory (using Ristretto)
```go
ristrettoCache, err := ristretto.NewCache(&ristretto.Config{NumCounters: 1000, MaxCost: 100, BufferItems: 64})
ristrettoCache, err := ristretto.NewCache(&ristretto.Config{
NumCounters: 1000,
MaxCost: 100,
BufferItems: 64,
})
if err != nil {
panic(err)
}
ristrettoStore := store.NewRistretto(ristrettoCache)
ristrettoStore := store.NewRistretto(ristrettoCache, nil)
cacheManager := cache.New(ristrettoStore, nil)
err := cacheManager.Set("my-key", "my-value")
cacheManager := cache.New(ristrettoStore)
err := cacheManager.Set("my-key", "my-value", &cache.Options{Cost: 2})
if err != nil {
panic(err)
}
Expand All @@ -89,10 +97,12 @@ value := cacheManager.Get("my-key")
#### Redis
```go
redisStore := store.NewRedis(redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"}))
redisStore := store.NewRedis(redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
}), nil)
cacheManager := cache.New(redisStore, &cache.Options{Expiration: 15*time.Second})
err := cacheManager.Set("my-key", "my-value")
cacheManager := cache.New(redisStore)
err := cacheManager.Set("my-key", "my-value", &cache.Options{Expiration: 15*time.Second})
if err != nil {
panic(err)
}
Expand All @@ -114,13 +124,13 @@ if err != nil {
redisClient := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
// Initialize stores
ristrettoStore := store.NewRistretto(ristrettoCache)
redisStore := store.NewRedis(redisClient)
ristrettoStore := store.NewRistretto(ristrettoCache, nil)
redisStore := store.NewRedis(redisClient, &cache.Optiobs{Expiration: 5*time.Second})
// Initialize chained cache
cacheManager := cache.NewChain(
cache.New(ristrettoStore, nil),
cache.New(redisStore, &cache.Options{Expiration: 15*time.Second}),
cache.New(ristrettoStore),
cache.New(redisStore),
)
// ... Then, do what you want with your cache
Expand All @@ -135,7 +145,7 @@ This cache will provide a load function that acts as a callable function and wil
```go
// Initialize Redis client and store
redisClient := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
redisStore := store.NewRedis(redisClient)
redisStore := store.NewRedis(redisClient, nil)
// Initialize a load function that loads your data from a custom source
loadFunction := func(key interface{}) (interface{}, error) {
Expand All @@ -146,7 +156,7 @@ loadFunction := func(key interface{}) (interface{}, error) {
// Initialize loadable cache
cacheManager := cache.NewLoadable(
loadFunction,
cache.New(redisStore, &cache.Options{Expiration: 15*time.Second}),
cache.New(redisStore),
)
// ... Then, you can get your data and your function will automatically put them in cache(s)
Expand All @@ -161,15 +171,15 @@ This cache will record metrics depending on the metric provider you pass to it.
```go
// Initialize Redis client and store
redisClient := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
redisStore := store.NewRedis(redisClient)
redisStore := store.NewRedis(redisClient, nil)
// Initializes Prometheus metrics service
promMetrics := metrics.NewPrometheus("my-test-app")
// Initialize metric cache
cacheManager := cache.NewMetric(
promMetrics,
cache.New(redisStore, &cache.Options{Expiration: 15*time.Second}),
cache.New(redisStore),
)
// ... Then, you can get your data and metrics will be observed by Prometheus
Expand All @@ -182,12 +192,12 @@ Some caches like Redis stores and returns the value as a string so you have to m
```go
// Initialize Redis client and store
redisClient := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
redisStore := store.NewRedis(redisClient)
redisStore := store.NewRedis(redisClient, nil)
// Initialize chained cache
cacheManager := cache.NewMetric(
promMetrics,
cache.New(redisStore, &cache.Options{Expiration: 15*time.Second}),
cache.New(redisStore),
)
// Initializes marshaler
Expand Down Expand Up @@ -242,13 +252,20 @@ func main() {
// Initialize Prometheus metrics collector
promMetrics := metrics.NewPrometheus("my-test-app")
ristrettoCache, err := ristretto.NewCache(&ristretto.Config{NumCounters: 1000, MaxCost: 100, BufferItems: 64})
// Initialize Ristretto store
ristrettoCache, err := ristretto.NewCache(&ristretto.Config{
NumCounters: 1000,
MaxCost: 100,
BufferItems: 64,
})
if err != nil {
panic(err)
}
ristrettoStore := store.NewRistretto(ristrettoCache)
redisStore := store.NewRedis(redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"}))
ristrettoStore := store.NewRistretto(ristrettoCache, &cache.Options{Cost: 4})
// Initialize Redis store
redisStore := store.NewRedis(redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"}), &cache.Options{Expiration: 5*time.Second})
// Initialize a load function that loads your data from a custom source
loadFunction := func(key interface{}) (interface{}, error) {
Expand All @@ -260,8 +277,8 @@ func main() {
// and a load function that will put data back into caches if none has the value
cacheManager := cache.NewMetric(promMetrics, cache.NewLoadable(loadFunction,
cache.NewChain(
cache.New(ristrettoStore, nil),
cache.New(redisStore, &cache.Options{Expiration: 15*time.Second}),
cache.New(ristrettoStore),
cache.New(redisStore),
),
))
Expand All @@ -270,7 +287,7 @@ func main() {
key := Book{Slug: "my-test-amazing-book"}
value := Book{ID: 1, Name: "My test amazing book", Slug: "my-test-amazing-book"}
err = marshaller.Set(key, value)
err = marshaller.Set(key, value, nil)
if err != nil {
panic(err)
}
Expand Down
12 changes: 3 additions & 9 deletions cache/cache.go
Expand Up @@ -14,18 +14,12 @@ const (
// Cache represents the configuration needed by a cache
type Cache struct {
codec codec.CodecInterface
options *Options
}

// New instanciates a new cache entry
func New(store store.StoreInterface, options *Options) *Cache {
if options == nil {
options = &Options{}
}

func New(store store.StoreInterface) *Cache {
return &Cache{
codec: codec.New(store),
options: options,
}
}

Expand All @@ -36,9 +30,9 @@ func (c *Cache) Get(key interface{}) (interface{}, error) {
}

// Set populates the cache item using the given key
func (c *Cache) Set(key, object interface{}) error {
func (c *Cache) Set(key, object interface{}, options *store.Options) error {
cacheKey := c.getCacheKey(key)
return c.codec.Set(cacheKey, object, c.options.ExpirationValue())
return c.codec.Set(cacheKey, object, options)
}

// GetCodec returns the current codec
Expand Down
45 changes: 14 additions & 31 deletions cache/cache_test.go
Expand Up @@ -6,6 +6,7 @@ import (
"time"

"github.com/eko/gache/codec"
"github.com/eko/gache/store"
mocksStore "github.com/eko/gache/test/mocks/store"

"github.com/stretchr/testify/assert"
Expand All @@ -14,24 +15,20 @@ import (
func TestNew(t *testing.T) {
// Given
store := &mocksStore.StoreInterface{}
options := &Options{
Expiration: 5 * time.Second,
}

// When
cache := New(store, options)
cache := New(store)

// Then
assert.IsType(t, new(Cache), cache)
assert.IsType(t, new(codec.Codec), cache.codec)

assert.Equal(t, store, cache.codec.GetStore())
assert.Equal(t, options, cache.options)
}

func TestCacheSet(t *testing.T) {
// Given
options := &Options{
options := &store.Options{
Expiration: 5 * time.Second,
}

Expand All @@ -42,19 +39,19 @@ func TestCacheSet(t *testing.T) {
}

store := &mocksStore.StoreInterface{}
store.On("Set", "9b1ac8a6e8ca8ca9477c0a252eb37756", value, options.ExpirationValue()).
store.On("Set", "9b1ac8a6e8ca8ca9477c0a252eb37756", value, options).
Return(nil)

cache := New(store, options)
cache := New(store)

// When
err := cache.Set("my-key", value)
err := cache.Set("my-key", value, options)
assert.Nil(t, err)
}

func TestCacheSetWhenErrorOccurs(t *testing.T) {
// Given
options := &Options{
options := &store.Options{
Expiration: 5 * time.Second,
}

Expand All @@ -67,22 +64,18 @@ func TestCacheSetWhenErrorOccurs(t *testing.T) {
storeErr := errors.New("An error has occured while inserting data into store")

store := &mocksStore.StoreInterface{}
store.On("Set", "9b1ac8a6e8ca8ca9477c0a252eb37756", value, options.ExpirationValue()).
store.On("Set", "9b1ac8a6e8ca8ca9477c0a252eb37756", value, options).
Return(storeErr)

cache := New(store, options)
cache := New(store)

// When
err := cache.Set("my-key", value)
err := cache.Set("my-key", value, options)
assert.Equal(t, storeErr, err)
}

func TestCacheGet(t *testing.T) {
// Given
options := &Options{
Expiration: 5 * time.Second,
}

cacheValue := &struct {
Hello string
}{
Expand All @@ -92,7 +85,7 @@ func TestCacheGet(t *testing.T) {
store := &mocksStore.StoreInterface{}
store.On("Get", "9b1ac8a6e8ca8ca9477c0a252eb37756").Return(cacheValue, nil)

cache := New(store, options)
cache := New(store)

// When
value, err := cache.Get("my-key")
Expand All @@ -104,16 +97,12 @@ func TestCacheGet(t *testing.T) {

func TestCacheGetWhenNotFound(t *testing.T) {
// Given
options := &Options{
Expiration: 5 * time.Second,
}

returnedErr := errors.New("Unable to find item in store")

store := &mocksStore.StoreInterface{}
store.On("Get", "9b1ac8a6e8ca8ca9477c0a252eb37756").Return(nil, returnedErr)

cache := New(store, options)
cache := New(store)

// When
value, err := cache.Get("my-key")
Expand All @@ -126,11 +115,8 @@ func TestCacheGetWhenNotFound(t *testing.T) {
func TestCacheGetCodec(t *testing.T) {
// Given
store := &mocksStore.StoreInterface{}
options := &Options{
Expiration: 5 * time.Second,
}

cache := New(store, options)
cache := New(store)

// When
value := cache.GetCodec()
Expand All @@ -143,11 +129,8 @@ func TestCacheGetCodec(t *testing.T) {
func TestCacheGetType(t *testing.T) {
// Given
store := &mocksStore.StoreInterface{}
options := &Options{
Expiration: 5 * time.Second,
}

cache := New(store, options)
cache := New(store)

// When - Then
assert.Equal(t, CacheType, cache.GetType())
Expand Down

0 comments on commit d28a9c2

Please sign in to comment.