Skip to content

Commit

Permalink
init source v1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
crosect-01 committed May 12, 2023
1 parent 3cffc30 commit 57312fd
Show file tree
Hide file tree
Showing 10 changed files with 1,286 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@

# Go workspace file
go.work
.idea
73 changes: 72 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,73 @@
# cc-go-cache
go cache

## Installation

```shell
go get github.com/crosect/cc-go-cache
```

## Configuration
```yaml
app.cache:
driver: "memory" #support memory, redis
# If use memory
memory:
defaultExpiration: "30s" # 30s, 30m, 30h
cleanupInterval: "1s" # 1s, 1m, 1h
# If use redis
redis:
host: localhost
port: 6379
database: 0
user: username
password: secret
enableTLS: true #default: false
```

## Usage

Register to fx container

```go
package bootstrap

import (
"github.com/crosect/cc-go-cache"
"go.uber.org/fx"
)

func Register() fx.Option {
return cccache.EnableCache()
}
```

Remember function will get value in the cache if exists, if not exists, it will set to cache

```go
package app

import (
"github.com/crosect/cc-go-cache"
"time"
)

type NeedCache struct {
cache *cccache.Cache
}

func (nc *NeedCache) UseRemember() {
// String
str, err := nc.cache.Remember("key", 30*time.Second, func() (interface{}, error) {
return "value", nil
})
// Number
num, err := nc.cache.Remember("key", 30*time.Second, func() (interface{}, error) {
return 100, nil
})
// Struct
value, err := nc.cache.Remember("key", 30*time.Second, func() (interface{}, error) {
return &Example{Data: "data"}, nil
})
data := value.(*Example).Data
}
```
79 changes: 79 additions & 0 deletions cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package cccache

import (
"context"
"github.com/crosect/cc-go/log"
"github.com/eko/gocache/v2/cache"
"github.com/eko/gocache/v2/store"
"time"
)

type Cache struct {
properties *CacheProperties
cache *cache.Cache
}

func NewCache(properties *CacheProperties) (*Cache, error) {
cacheStore, err := NewStore(properties)
if err != nil {
return nil, err
}
c := cache.New(cacheStore)
return &Cache{
properties: properties,
cache: c,
}, nil
}

func (c *Cache) Exist(key string) bool {
_, err := c.Get(key)
return err == nil
}

func (c *Cache) Remember(key string, duration time.Duration, fn func() (interface{}, error)) (interface{}, error) {
value, err := c.Get(key)
if err == nil {
return value, nil
}
v, err := fn()
if err == nil {
c.AsyncSet(key, v, duration)
}
return v, err
}

func (c *Cache) AsyncSet(key string, value interface{}, duration time.Duration) {
go func() {
err := c.cache.Set(context.Background(), key, value, &store.Options{
Expiration: duration,
})
if err != nil {
log.Warnf("cache: async set: %v", err)
}
}()
}

func (c *Cache) Get(key string) (interface{}, error) {
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancel()
value, err := c.cache.Get(ctx, key)
if err != nil {
log.Debugf("cache: get by key: %v: %v", key, err)
return nil, err
}
return value, nil
}

func (c *Cache) Set(key string, value interface{}, duration time.Duration) error {
return c.cache.Set(context.Background(), key, value, &store.Options{
Expiration: duration,
})
}

func (c *Cache) Delete(key string) error {
return c.cache.Delete(context.Background(), key)
}

func (c *Cache) Clear() error {
return c.cache.Clear(context.Background())
}
41 changes: 41 additions & 0 deletions cache_properties.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package cccache

import (
"github.com/crosect/cc-go/config"
"time"
)

// CacheProperties represents ...
type CacheProperties struct {
Driver string
Memory MemoryCacheProperties
Redis RedisCacheProperties
}

// MemoryCacheProperties represent memory cache properties
type MemoryCacheProperties struct {
DefaultExpiration time.Duration `default:"30s"`
CleanupInterval time.Duration `default:"30s"`
}

// RedisCacheProperties represents redis cache properties
type RedisCacheProperties struct {
Host string
Port int
Database int
User string
Password string
EnableTLS bool
}

// NewCacheProperties return a new CacheProperties instance
func NewCacheProperties(loader config.Loader) (*CacheProperties, error) {
props := CacheProperties{}
err := loader.Bind(&props)
return &props, err
}

// Prefix return config prefix
func (t *CacheProperties) Prefix() string {
return "app.cache"
}
40 changes: 40 additions & 0 deletions cache_store_factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package cccache

import (
"crypto/tls"
"fmt"
"github.com/eko/gocache/v2/store"
"github.com/go-redis/redis/v8"
gc "github.com/patrickmn/go-cache"
)

func NewStore(properties *CacheProperties) (store.StoreInterface, error) {
switch properties.Driver {
case "memory":
return NewMemoryStore(properties.Memory)
case "redis":
return NewRedisStore(properties.Redis)
default:
return nil, fmt.Errorf("cache driver: %s is not supported", properties.Driver)
}
}

func NewMemoryStore(properties MemoryCacheProperties) (store.StoreInterface, error) {
client := gc.New(properties.DefaultExpiration, properties.CleanupInterval)
return store.NewGoCache(client, nil), nil
}

func NewRedisStore(properties RedisCacheProperties) (store.StoreInterface, error) {
options := &redis.Options{
Addr: fmt.Sprintf("%s:%d", properties.Host, properties.Port),
Username: properties.User,
Password: properties.Password,
DB: properties.Database,
}
if properties.EnableTLS {
options.TLSConfig = &tls.Config{
MinVersion: tls.VersionTLS12,
}
}
return store.NewRedis(redis.NewClient(options), nil), nil
}
155 changes: 155 additions & 0 deletions cache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package cccache

import (
"context"
"github.com/eko/gocache/v2/store"
"github.com/stretchr/testify/assert"
"testing"
"time"
)

type Data struct {
Value string
}

func TestCache_Exist(t *testing.T) {
cache, err := NewCache(&CacheProperties{
Driver: "memory",
Memory: MemoryCacheProperties{
DefaultExpiration: time.Minute,
CleanupInterval: 30 * time.Second,
},
})
assert.Nil(t, err)
t.Run("Not Exist", func(t *testing.T) {
exist := cache.Exist("not_exist_key")
assert.False(t, exist)
})
t.Run("Exist", func(t *testing.T) {
err := cache.cache.Set(context.Background(), "exist_key", "value", &store.Options{Expiration: time.Minute})
assert.Nil(t, err)
exist := cache.Exist("exist_key")
assert.True(t, exist)
})
}

func TestCache_Remember(t *testing.T) {
cache, err := NewCache(&CacheProperties{
Driver: "memory",
Memory: MemoryCacheProperties{
DefaultExpiration: time.Minute,
CleanupInterval: 30 * time.Second,
},
})
assert.Nil(t, err)
t.Run("Not Exist With Integer", func(t *testing.T) {
err := cache.cache.Delete(context.Background(), "not_exist_key")
assert.Nil(t, err)
value, err := cache.Remember("not_exist_key", time.Minute, func() (interface{}, error) {
return 10, nil
})
assert.Nil(t, err)
assert.Equal(t, 10, value)
time.Sleep(200 * time.Millisecond)
v, err := cache.cache.Get(context.Background(), "not_exist_key")
assert.Nil(t, err)
assert.Equal(t, 10, v)
})
t.Run("Not Exist With String", func(t *testing.T) {
err := cache.cache.Delete(context.Background(), "not_exist_key")
assert.Nil(t, err)
value, err := cache.Remember("not_exist_key", time.Minute, func() (interface{}, error) {
return "string", nil
})
assert.Nil(t, err)
assert.Equal(t, "string", value)
time.Sleep(200 * time.Millisecond)
v, err := cache.cache.Get(context.Background(), "not_exist_key")
assert.Nil(t, err)
assert.Equal(t, "string", v)
})
t.Run("Not Exist With Struct", func(t *testing.T) {
err := cache.cache.Delete(context.Background(), "not_exist_key")
assert.Nil(t, err)
value, err := cache.Remember("not_exist_key", time.Minute, func() (interface{}, error) {
return &Data{Value: "value"}, nil
})
assert.Nil(t, err)
assert.Equal(t, "value", value.(*Data).Value)
time.Sleep(200 * time.Millisecond)
v, err := cache.cache.Get(context.Background(), "not_exist_key")
assert.Nil(t, err)
assert.Equal(t, "value", v.(*Data).Value)
})
t.Run("Exist With Integer", func(t *testing.T) {
err := cache.cache.Set(context.Background(), "exist_key", 10, &store.Options{Expiration: time.Minute})
assert.Nil(t, err)
value, err := cache.Remember("exist_key", time.Minute, func() (interface{}, error) {
return 10, nil
})
assert.Nil(t, err)
assert.Equal(t, 10, value)
})
t.Run("Exist With String", func(t *testing.T) {
err := cache.cache.Set(context.Background(), "exist_key", "string", &store.Options{Expiration: time.Minute})
assert.Nil(t, err)
value, err := cache.Remember("exist_key", time.Minute, func() (interface{}, error) {
return "string", nil
})
assert.Nil(t, err)
assert.Equal(t, "string", value)
})
t.Run("Exist With Struct", func(t *testing.T) {
err := cache.cache.Set(context.Background(), "exist_key", &Data{Value: "value"}, &store.Options{Expiration: time.Minute})
assert.Nil(t, err)
value, err := cache.Remember("exist_key", time.Minute, func() (interface{}, error) {
return &Data{Value: "value"}, nil
})
assert.Nil(t, err)
assert.Equal(t, "value", value.(*Data).Value)
})
}

func TestCache_Delete(t *testing.T) {
cache, err := NewCache(&CacheProperties{
Driver: "memory",
Memory: MemoryCacheProperties{
DefaultExpiration: time.Minute,
CleanupInterval: 30 * time.Second,
},
})
assert.Nil(t, err)
t.Run("Delete", func(t *testing.T) {
setErr := cache.cache.Set(context.Background(), "delete_key", "value", &store.Options{Expiration: time.Minute})
assert.Nil(t, setErr)
beforeDelete := cache.Exist("delete_key")
assert.True(t, beforeDelete)
err := cache.Delete("delete_key")
assert.Nil(t, err)
afterDelete := cache.Exist("delete_key")
assert.False(t, afterDelete)
})
}

func TestCache_Clear(t *testing.T) {
cache, err := NewCache(&CacheProperties{
Driver: "memory",
Memory: MemoryCacheProperties{
DefaultExpiration: time.Minute,
CleanupInterval: 30 * time.Second,
},
})
assert.Nil(t, err)
t.Run("Clear", func(t *testing.T) {
setErr := cache.cache.Set(context.Background(), "first_key", "value", &store.Options{Expiration: time.Minute})
assert.Nil(t, setErr)
setErr = cache.cache.Set(context.Background(), "second_key", "value", &store.Options{Expiration: time.Minute})
assert.Nil(t, setErr)
assert.True(t, cache.Exist("first_key"))
assert.True(t, cache.Exist("second_key"))
err := cache.Clear()
assert.Nil(t, err)
assert.False(t, cache.Exist("first_key"))
assert.False(t, cache.Exist("second_key"))
})
}

0 comments on commit 57312fd

Please sign in to comment.