Skip to content

Commit

Permalink
Merge pull request #2 from eko/bigcache
Browse files Browse the repository at this point in the history
feat(store): added bigcache
  • Loading branch information
eko committed Oct 13, 2019
2 parents 52a29a1 + 4e751dc commit 0d1032b
Show file tree
Hide file tree
Showing 10 changed files with 288 additions and 41 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -15,4 +15,4 @@ install:
- go get -t -v ./...

script:
- go test -v ./...
- go test -cover -v ./...
1 change: 1 addition & 0 deletions Makefile
Expand Up @@ -6,6 +6,7 @@ mocks:
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/
61 changes: 45 additions & 16 deletions README.md
Expand Up @@ -19,7 +19,8 @@ Here is what it brings in detail:

## Built-in stores

* [Memory](https://github.com/dgraph-io/ristretto) (dgraph-io/ristretto)
* [Memory (bigcache)](https://github.com/allegro/bigcache) (allegro/bigcache)
* [Memory (ristretto)](https://github.com/dgraph-io/ristretto) (dgraph-io/ristretto)
* [Memcache](https://github.com/bradfitz/gomemcache) (bradfitz/memcache)
* [Redis](https://github.com/go-redis/redis/v7) (go-redis/redis)
* More to come soon
Expand All @@ -37,9 +38,11 @@ Here is a simple cache instanciation with Redis but you can also look at other a
#### Memcache

```go
memcacheStore := store.NewMemcache(memcache.New("10.0.0.1:11211", "10.0.0.2:11211", "10.0.0.3:11212"))
memcacheStore := store.NewMemcache(
memcache.New("10.0.0.1:11211", "10.0.0.2:11211", "10.0.0.3:11212"),
)

cacheManager := cache.New(memcacheStore, 15*time.Second)
cacheManager := cache.New(memcacheStore, &cache.Options{Expiration: 15*time.Second})
err := cacheManager.Set("my-key", []byte("my-value))
if err != nil {
panic(err)
Expand All @@ -48,6 +51,23 @@ if err != nil {
value := cacheManager.Get("my-key")
```
#### Memory (using Bigcache)
```go
bigcacheClient, _ := bigcache.NewBigCache(bigcache.DefaultConfig(5 * time.Minute))
bigcacheStore := store.NewBigcache(
bigcacheClient,
)
cacheManager := cache.New(bigcacheStore, nil)
err := cacheManager.Set("my-key", "my-value")
if err != nil {
panic(err)
}
value := cacheManager.Get("my-key")
```
#### Memory (using Ristretto)
```go
Expand All @@ -57,8 +77,8 @@ if err != nil {
}
ristrettoStore := store.NewRistretto(ristrettoCache)
cacheManager := cache.New(ristrettoStore, 1*time.Second)
err := cacheManager.Set("my-key", "my-value)
cacheManager := cache.New(ristrettoStore, nil)
err := cacheManager.Set("my-key", "my-value")
if err != nil {
panic(err)
}
Expand All @@ -71,8 +91,8 @@ value := cacheManager.Get("my-key")
```go
redisStore := store.NewRedis(redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"}))
cacheManager := cache.New(redisStore, 15*time.Second)
err := cacheManager.Set("my-key", "my-value)
cacheManager := cache.New(redisStore, &cache.Options{Expiration: 15*time.Second})
err := cacheManager.Set("my-key", "my-value")
if err != nil {
panic(err)
}
Expand All @@ -99,8 +119,8 @@ redisStore := store.NewRedis(redisClient)
// Initialize chained cache
cacheManager := cache.NewChain(
cache.New(ristrettoStore, 5*time.Second),
cache.New(redisStore, 15*time.Second),
cache.New(ristrettoStore, nil),
cache.New(redisStore, &cache.Options{Expiration: 15*time.Second}),
)
// ... Then, do what you want with your cache
Expand All @@ -124,11 +144,16 @@ loadFunction := func(key interface{}) (interface{}, error) {
}
// Initialize loadable cache
cacheManager := cache.NewLoadable(loadFunction, cache.New(redisStore, 15*time.Second))
cacheManager := cache.NewLoadable(
loadFunction,
cache.New(redisStore, &cache.Options{Expiration: 15*time.Second}),
)
// ... Then, you can get your data and your function will automatically put them in cache(s)
```
Of course, you can also pass a `Chain` cache into the `Loadable` one so if your data is not available in all caches, it will bring it back in all caches.
### A metric cache to retrieve cache statistics
This cache will record metrics depending on the metric provider you pass to it. Here we give a Prometheus provider:
Expand All @@ -142,13 +167,14 @@ redisStore := store.NewRedis(redisClient)
promMetrics := metrics.NewPrometheus("my-test-app")
// Initialize metric cache
cacheManager := cache.NewMetric(promMetrics, cache.New(redisStore, 15*time.Second))
cacheManager := cache.NewMetric(
promMetrics,
cache.New(redisStore, &cache.Options{Expiration: 15*time.Second}),
)
// ... Then, you can get your data and metrics will be observed by Prometheus
```
Of course, you can pass a `Chain` cache into the `Loadable` one so if your data is not available in all caches, it will bring it back in all caches.
### A marshaler wrapper
Some caches like Redis stores and returns the value as a string so you have to marshal/unmarshal your structs if you want to cache an object. That's why we bring a marshaler service that wraps your cache and make the work for you:
Expand All @@ -159,7 +185,10 @@ redisClient := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
redisStore := store.NewRedis(redisClient)
// Initialize chained cache
cacheManager := cache.NewMetric(promMetrics, cache.New(redisStore, 15*time.Second))
cacheManager := cache.NewMetric(
promMetrics,
cache.New(redisStore, &cache.Options{Expiration: 15*time.Second}),
)
// Initializes marshaler
marshaller := marshaler.New(cacheManager)
Expand Down Expand Up @@ -231,8 +260,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, 5*time.Second),
cache.New(redisStore, 15*time.Second),
cache.New(ristrettoStore, nil),
cache.New(redisStore, &cache.Options{Expiration: 15*time.Second}),
),
))
Expand Down
17 changes: 10 additions & 7 deletions cache/cache.go
Expand Up @@ -2,7 +2,6 @@ package cache

import (
"strings"
"time"

"github.com/eko/gache/codec"
"github.com/eko/gache/store"
Expand All @@ -14,15 +13,19 @@ const (

// Cache represents the configuration needed by a cache
type Cache struct {
codec codec.CodecInterface
expiration time.Duration
codec codec.CodecInterface
options *Options
}

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

return &Cache{
codec: codec.New(store),
expiration: expiration,
codec: codec.New(store),
options: options,
}
}

Expand All @@ -35,7 +38,7 @@ 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 {
cacheKey := c.getCacheKey(key)
return c.codec.Set(cacheKey, object, c.expiration)
return c.codec.Set(cacheKey, object, c.options.ExpirationValue())
}

// GetCodec returns the current codec
Expand Down
50 changes: 33 additions & 17 deletions cache/cache_test.go
Expand Up @@ -14,22 +14,26 @@ import (
func TestNew(t *testing.T) {
// Given
store := &mocksStore.StoreInterface{}
expiration := 5 * time.Second
options := &Options{
Expiration: 5 * time.Second,
}

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

// 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, expiration, cache.expiration)
assert.Equal(t, options, cache.options)
}

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

value := &struct {
Hello string
Expand All @@ -38,9 +42,10 @@ func TestCacheSet(t *testing.T) {
}

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

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

// When
err := cache.Set("my-key", value)
Expand All @@ -49,7 +54,9 @@ func TestCacheSet(t *testing.T) {

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

value := &struct {
Hello string
Expand All @@ -60,9 +67,10 @@ 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, expiration).Return(storeErr)
store.On("Set", "9b1ac8a6e8ca8ca9477c0a252eb37756", value, options.ExpirationValue()).
Return(storeErr)

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

// When
err := cache.Set("my-key", value)
Expand All @@ -71,7 +79,9 @@ func TestCacheSetWhenErrorOccurs(t *testing.T) {

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

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

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

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

func TestCacheGetWhenNotFound(t *testing.T) {
// Given
expiration := 5 * time.Second
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, expiration)
cache := New(store, options)

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

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

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

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

// When - Then
assert.Equal(t, CacheType, cache.GetType())
Expand Down
15 changes: 15 additions & 0 deletions cache/options.go
@@ -0,0 +1,15 @@
package cache

import (
"time"
)

// Options represents the cache available options
type Options struct {
Expiration time.Duration
}

// ExpirationValue returns the expiration option value
func (o Options) ExpirationValue() time.Duration {
return o.Expiration
}
18 changes: 18 additions & 0 deletions cache/options_test.go
@@ -0,0 +1,18 @@
package cache

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
)

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

// When - Then
assert.Equal(t, 25*time.Second, options.ExpirationValue())
}

0 comments on commit 0d1032b

Please sign in to comment.