Skip to content

Commit

Permalink
Merge branch 'master' into 79-feat-implements-and-test-go-122-router-…
Browse files Browse the repository at this point in the history
…and-analisys-performance-versus-fiber
  • Loading branch information
arkanjoms committed May 7, 2024
2 parents 6e3bded + b9c6439 commit 9a7330b
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 37 deletions.
134 changes: 125 additions & 9 deletions pkg/database/cacheDB/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,49 @@ import (
"encoding/json"
"errors"
"fmt"
"strings"
"time"

"github.com/colibri-project-io/colibri-sdk-go/pkg/base/config"
)

const (
errRedisNil string = "redis: nil"
errRedisMoved string = "MOVED"
)

// Cache struct
type Cache[T any] struct {
name string
ttl time.Duration
}

// NewCache create a new pointer to Cache struct.
// NewCache creates a new pointer to Cache struct.
//
// Parameters:
// - name: a string representing the name of the cache.
// - ttl: a time.Duration representing the time to live for the cache items.
// Returns a pointer to Cache[T].
func NewCache[T any](name string, ttl time.Duration) *Cache[T] {
return &Cache[T]{name, ttl}
}

// Many returns a slice of T value
// Many retrieves multiple items of type T from the cache.
//
// ctx: The context for the cache operation.
// Returns a slice of retrieved items of type T and an error.
func (c *Cache[T]) Many(ctx context.Context) ([]T, error) {
if err := c.validate(); err != nil {
return nil, err
}

result, err := instance.Get(ctx, c.getNamePrefixed()).Bytes()
result, err := c.get(ctx)
if err != nil {
return nil, err
}
if result == nil {
return nil, nil
}

list := make([]T, 0)
if err = json.Unmarshal(result, &list); err != nil {
Expand All @@ -40,16 +57,22 @@ func (c *Cache[T]) Many(ctx context.Context) ([]T, error) {
return list, nil
}

// One return a pointer of T value
// One retrieves a single item of type T from the cache.
//
// ctx: The context for the cache operation.
// Returns a pointer to the retrieved item of type T and an error.
func (c *Cache[T]) One(ctx context.Context) (*T, error) {
if err := c.validate(); err != nil {
return nil, err
}

result, err := instance.Get(ctx, c.getNamePrefixed()).Bytes()
result, err := c.get(ctx)
if err != nil {
return nil, err
}
if result == nil {
return nil, nil
}

model := new(T)
if err = json.Unmarshal(result, &model); err != nil {
Expand All @@ -59,7 +82,11 @@ func (c *Cache[T]) One(ctx context.Context) (*T, error) {
return model, nil
}

// Set save data in cacheDB
// Set save data in cacheDB.
//
// ctx: The context for the cache operation.
// data: The data to be saved in the cache.
// Returns an error.
func (c *Cache[T]) Set(ctx context.Context, data any) error {
if err := c.validate(); err != nil {
return err
Expand All @@ -70,18 +97,25 @@ func (c *Cache[T]) Set(ctx context.Context, data any) error {
return err
}

return instance.Set(ctx, c.getNamePrefixed(), jsonData, c.ttl).Err()
return c.set(ctx, jsonData)
}

// Del delete data in cachedDB
// Del delete data in cachedDB.
//
// ctx: The context for the cache operation.
// Returns an error.
func (c *Cache[T]) Del(ctx context.Context) error {
if err := c.validate(); err != nil {
return err
}

return instance.Del(ctx, c.getNamePrefixed()).Err()
return c.del(ctx)
}

// validate checks if the cache is initialized and has a name.
//
// No parameters.
// Returns an error.
func (c *Cache[T]) validate() error {
if instance == nil {
return errors.New("Cache not initialized")
Expand All @@ -94,6 +128,88 @@ func (c *Cache[T]) validate() error {
return nil
}

// getNamePrefixed returns a string with the prefixed name using the application name and cache name.
//
// No parameters.
// Returns a string.
func (c *Cache[T]) getNamePrefixed() string {
return fmt.Sprintf("%s::%s", config.APP_NAME, c.name)
}

// isErrRedisMoved checks if the error contains the string "MOVED".
//
// Parameter:
// - err: The error to check.
// Return type: bool
func (c *Cache[T]) isErrRedisMoved(err error) bool {
return strings.Contains(err.Error(), errRedisMoved)
}

// reconectInstanceAfterError updates the address of the instance based on the last element of the error message.
//
// Parameter:
// - err: The error that triggered the reconnection.
func (c *Cache[T]) reconectInstanceAfterError(err error) {
movedSetInfo := strings.Split(err.Error(), " ")
instance.Options().Addr = movedSetInfo[len(movedSetInfo)-1]
}

// get retrieves data from the cache and handles errors including redis MOVED error.
//
// ctx: The context for the cache operation.
// Returns a byte slice and an error.
func (c *Cache[T]) get(ctx context.Context) ([]byte, error) {
for {
result, err := instance.Get(ctx, c.getNamePrefixed()).Bytes()
if err != nil {
if err.Error() == errRedisNil {
return nil, nil
} else if c.isErrRedisMoved(err) {
c.reconectInstanceAfterError(err)
continue
} else {
return nil, err
}
}
return result, nil
}
}

// set saves data in the cacheDB.
//
// ctx: The context for the cache operation.
// data: The data to be saved in the cache.
// Returns an error.
func (c *Cache[T]) set(ctx context.Context, data []byte) error {
for {
err := instance.Set(ctx, c.getNamePrefixed(), data, c.ttl).Err()
if err != nil {
if c.isErrRedisMoved(err) {
c.reconectInstanceAfterError(err)
continue
} else {
return err
}
}
return nil
}
}

// del deletes data in cachedDB.
//
// ctx: The context for the cache operation.
// Returns an error.
func (c *Cache[T]) del(ctx context.Context) error {
for {
err := instance.Del(ctx, c.getNamePrefixed()).Err()
if err != nil {
if c.isErrRedisMoved(err) {
c.reconectInstanceAfterError(err)
continue
} else {
return err
}
}
return nil
}
}
33 changes: 20 additions & 13 deletions pkg/database/cacheDB/cache_db.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,12 @@ import (

type cacheDBObserver struct{}

func (o cacheDBObserver) Close() {
logging.Info("waiting to safely close the cache connection")
if observer.WaitRunningTimeout() {
logging.Warn("WaitGroup timed out, forcing close the cache connection")
}

logging.Info("closing cache connection")
if err := instance.Close(); err != nil {
logging.Error("error when closing cache connection: %v", err)
}
}

var instance *redis.Client

// Initialize initializes the cache database connection.
//
// No parameters.
// No return values.
func Initialize() {
opts := &redis.Options{Addr: config.CACHE_URI, Password: config.CACHE_PASSWORD}

Expand All @@ -36,7 +28,22 @@ func Initialize() {
}

instance = redisClient
logging.Info("Cache database connected")
observer.Attach(cacheDBObserver{})
logging.Info("Cache database connected")
}

// Close closes the cache connection safely.
//
// No parameters.
// No return values.
func (o cacheDBObserver) Close() {
logging.Info("waiting to safely close the cache connection")
if observer.WaitRunningTimeout() {
logging.Warn("WaitGroup timed out, forcing close the cache connection")
}

logging.Info("closing cache connection")
if err := instance.Close(); err != nil {
logging.Error("error when closing cache connection: %v", err)
}
}
31 changes: 16 additions & 15 deletions pkg/database/cacheDB/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func TestCacheNotInitializedValidation(t *testing.T) {
}

func TestCache(t *testing.T) {
ctx := context.Background()
expected := []userCached{
{Id: 1, Name: "User 1"},
{Id: 2, Name: "User 2"},
Expand All @@ -40,31 +41,31 @@ func TestCache(t *testing.T) {
assert.NotNil(t, result.ttl)
})

t.Run("Should return error when cache name is empty value", func(t *testing.T) {
t.Run("Should return error when cache name is empty value on call many", func(t *testing.T) {
cache := Cache[userCached]{}

_, err := cache.Many(context.Background())
_, err := cache.Many(ctx)
assert.NotNil(t, err)
})

t.Run("Should return error when cache name is empty value on call one", func(t *testing.T) {
cache := Cache[userCached]{}

_, err := cache.One(context.Background())
_, err := cache.One(ctx)
assert.NotNil(t, err)
})

t.Run("Should return error when cache name is empty value on call set", func(t *testing.T) {
cache := Cache[userCached]{}

err := cache.Set(context.Background(), cache)
err := cache.Set(ctx, cache)
assert.NotNil(t, err)
})

t.Run("Should return error when cache name is empty value on call del", func(t *testing.T) {
cache := Cache[userCached]{}

err := cache.Del(context.Background())
err := cache.Del(ctx)
assert.NotNil(t, err)
})

Expand All @@ -74,17 +75,17 @@ func TestCache(t *testing.T) {
"invalid": make(chan int),
}

err := cache.Set(context.Background(), invalid)
err := cache.Set(ctx, invalid)
assert.NotNil(t, err)
})

t.Run("Should set many data in cache", func(t *testing.T) {
cache := NewCache[userCached]("cache-test", time.Hour)

err := cache.Set(context.Background(), expected)
err := cache.Set(ctx, expected)
assert.Nil(t, err)

result, err := cache.Many(context.Background())
result, err := cache.Many(ctx)
assert.Nil(t, err)
assert.NotNil(t, result)
assert.Len(t, result, 3)
Expand All @@ -94,10 +95,10 @@ func TestCache(t *testing.T) {
t.Run("Should set one data in cache", func(t *testing.T) {
cache := NewCache[userCached]("cache-test", time.Hour)

err := cache.Set(context.Background(), expected[0])
err := cache.Set(ctx, expected[0])
assert.Nil(t, err)

result, err := cache.One(context.Background())
result, err := cache.One(ctx)
assert.Nil(t, err)
assert.NotNil(t, result)
assert.Equal(t, expected[0], *result)
Expand All @@ -106,20 +107,20 @@ func TestCache(t *testing.T) {
t.Run("Should del data in cache", func(t *testing.T) {
cache := NewCache[userCached]("cache-test", time.Hour)

err := cache.Set(context.Background(), expected)
err := cache.Set(ctx, expected)
assert.Nil(t, err)

result, err := cache.Many(context.Background())
result, err := cache.Many(ctx)
assert.Nil(t, err)
assert.NotNil(t, result)
assert.Len(t, result, 3)
assert.Equal(t, expected, result)

err = cache.Del(context.Background())
err = cache.Del(ctx)
assert.Nil(t, err)

result, err = cache.Many(context.Background())
assert.NotNil(t, err)
result, err = cache.Many(ctx)
assert.Nil(t, err)
assert.Nil(t, result)
})
}

0 comments on commit 9a7330b

Please sign in to comment.