Skip to content

Commit

Permalink
Merge pull request #203 from semihbkgr/hz-refactoring
Browse files Browse the repository at this point in the history
Hazelcast store refactoring
  • Loading branch information
eko committed Mar 26, 2023
2 parents 730d2e5 + d494c4a commit 6d3bfb5
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 53 deletions.
13 changes: 5 additions & 8 deletions README.md
Expand Up @@ -168,7 +168,7 @@ redisStore := redis_store.NewRedis(redis.NewClient(&redis.Options{
}))

cacheManager := cache.New[string](redisStore)
err := cacheManager.Set("my-key", "my-value", store.WithExpiration(15*time.Second))
err := cacheManager.Set(ctx, "my-key", "my-value", store.WithExpiration(15*time.Second))
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -247,20 +247,17 @@ value, _ := cacheManager.Get(ctx, "my-key")
#### Hazelcast

```go
client, err := hazelcast.StartNewClient(ctx)
hzClient, err := hazelcast.StartNewClient(ctx)
if err != nil {
log.Fatalf("Failed to start client: %v", err)
}

hzMap, err := client.GetMap(ctx, "gocache")
if err != nil {
log.Fatalf("Failed to get map: %v", err)
}
hzMapName:= "gocache"

hazelcastStore := hazelcast_store.NewHazelcast(hzMap)
hazelcastStore := hazelcast_store.NewHazelcast(hzClient, hzMapName)

cacheManager := cache.New[string](hazelcastStore)
err := cacheManager.Set("my-key", "my-value", store.WithExpiration(15*time.Second))
err := cacheManager.Set(ctx, "my-key", "my-value", store.WithExpiration(15*time.Second))
if err != nil {
panic(err)
}
Expand Down
73 changes: 56 additions & 17 deletions store/hazelcast/hazelcast.go
Expand Up @@ -8,6 +8,7 @@ import (
"time"

lib_store "github.com/eko/gocache/lib/v4/store"
"github.com/hazelcast/hazelcast-go-client"
"github.com/hazelcast/hazelcast-go-client/types"
"golang.org/x/sync/errgroup"
)
Expand All @@ -21,6 +22,8 @@ type HazelcastMapInterface interface {
Clear(ctx context.Context) error
}

type HazelcastMapInterfaceProvider func(ctx context.Context) (HazelcastMapInterface, error)

const (
// HazelcastType represents the storage type as a string value
HazelcastType = "hazelcast"
Expand All @@ -32,21 +35,37 @@ const (

// HazelcastStore is a store for Hazelcast
type HazelcastStore struct {
hzMap HazelcastMapInterface
options *lib_store.Options
mapProvider HazelcastMapInterfaceProvider
options *lib_store.Options
}

// NewHazelcast creates a new store to Hazelcast instance(s)
func NewHazelcast(hzMap HazelcastMapInterface, options ...lib_store.Option) *HazelcastStore {
func NewHazelcast(hzClient *hazelcast.Client, mapName string, options ...lib_store.Option) *HazelcastStore {
return &HazelcastStore{
mapProvider: func(ctx context.Context) (HazelcastMapInterface, error) {
return hzClient.GetMap(ctx, mapName)
},
options: lib_store.ApplyOptions(options...),
}
}

// newHazelcast creates a new store with given HazelcastMapInterface for test purpose
func newHazelcast(hzMap HazelcastMapInterface, options ...lib_store.Option) *HazelcastStore {
return &HazelcastStore{
hzMap: hzMap,
mapProvider: func(ctx context.Context) (HazelcastMapInterface, error) {
return hzMap, nil
},
options: lib_store.ApplyOptions(options...),
}
}

// Get returns data stored from a given key
func (s *HazelcastStore) Get(ctx context.Context, key any) (any, error) {
value, err := s.hzMap.Get(ctx, key)
hzMap, err := s.mapProvider(ctx)
if err != nil {
return nil, err
}
value, err := hzMap.Get(ctx, key)
if err != nil {
return nil, err
}
Expand All @@ -58,7 +77,11 @@ func (s *HazelcastStore) Get(ctx context.Context, key any) (any, error) {

// GetWithTTL returns data stored from a given key and its corresponding TTL
func (s *HazelcastStore) GetWithTTL(ctx context.Context, key any) (any, time.Duration, error) {
entryView, err := s.hzMap.GetEntryView(ctx, key)
hzMap, err := s.mapProvider(ctx)
if err != nil {
return nil, 0, err
}
entryView, err := hzMap.GetEntryView(ctx, key)
if err != nil {
return nil, 0, err
}
Expand All @@ -71,28 +94,32 @@ func (s *HazelcastStore) GetWithTTL(ctx context.Context, key any) (any, time.Dur
// Set defines data in Hazelcast for given key identifier
func (s *HazelcastStore) Set(ctx context.Context, key any, value any, options ...lib_store.Option) error {
opts := lib_store.ApplyOptionsWithDefault(s.options, options...)
err := s.hzMap.SetWithTTL(ctx, key, value, opts.Expiration)
hzMap, err := s.mapProvider(ctx)
if err != nil {
return err
}
err = hzMap.SetWithTTL(ctx, key, value, opts.Expiration)
if err != nil {
return err
}
if tags := opts.Tags; len(tags) > 0 {
s.setTags(ctx, key, tags)
s.setTags(ctx, hzMap, key, tags)
}
return nil
}

func (s *HazelcastStore) setTags(ctx context.Context, key any, tags []string) {
func (s *HazelcastStore) setTags(ctx context.Context, hzMap HazelcastMapInterface, key any, tags []string) {
group, ctx := errgroup.WithContext(ctx)
for _, tag := range tags {
currentTag := tag
group.Go(func() error {
tagKey := fmt.Sprintf(HazelcastTagPattern, currentTag)
tagValue, err := s.hzMap.Get(ctx, tagKey)
tagValue, err := hzMap.Get(ctx, tagKey)
if err != nil {
return err
}
if tagValue == nil {
return s.hzMap.SetWithTTL(ctx, tagKey, key.(string), TagKeyExpiry)
return hzMap.SetWithTTL(ctx, tagKey, key.(string), TagKeyExpiry)
}
cacheKeys := strings.Split(tagValue.(string), ",")
for _, cacheKey := range cacheKeys {
Expand All @@ -102,41 +129,53 @@ func (s *HazelcastStore) setTags(ctx context.Context, key any, tags []string) {
}
cacheKeys = append(cacheKeys, key.(string))
newTagValue := strings.Join(cacheKeys, ",")
return s.hzMap.SetWithTTL(ctx, tagKey, newTagValue, TagKeyExpiry)
return hzMap.SetWithTTL(ctx, tagKey, newTagValue, TagKeyExpiry)
})
}
group.Wait()
}

// Delete removes data from Hazelcast for given key identifier
func (s *HazelcastStore) Delete(ctx context.Context, key any) error {
_, err := s.hzMap.Remove(ctx, key)
hzMap, err := s.mapProvider(ctx)
if err != nil {
return err
}
_, err = hzMap.Remove(ctx, key)
return err
}

// Invalidate invalidates some cache data in Hazelcast for given options
func (s *HazelcastStore) Invalidate(ctx context.Context, options ...lib_store.InvalidateOption) error {
opts := lib_store.ApplyInvalidateOptions(options...)
if tags := opts.Tags; len(tags) > 0 {
hzMap, err := s.mapProvider(ctx)
if err != nil {
return err
}
for _, tag := range tags {
tagKey := fmt.Sprintf(HazelcastTagPattern, tag)
tagValue, err := s.hzMap.Get(ctx, tagKey)
tagValue, err := hzMap.Get(ctx, tagKey)
if err != nil || tagValue == nil {
continue
}
cacheKeys := strings.Split(tagValue.(string), ",")
for _, cacheKey := range cacheKeys {
s.Delete(ctx, cacheKey)
hzMap.Remove(ctx, cacheKey)
}
s.Delete(ctx, tagKey)
hzMap.Remove(ctx, tagKey)
}
}
return nil
}

// Clear resets all data in the store
func (s *HazelcastStore) Clear(ctx context.Context) error {
return s.hzMap.Clear(ctx)
hzMap, err := s.mapProvider(ctx)
if err != nil {
return err
}
return hzMap.Clear(ctx)
}

// GetType returns the store type
Expand Down
18 changes: 4 additions & 14 deletions store/hazelcast/hazelcast_benchmark_test.go
Expand Up @@ -13,17 +13,12 @@ import (
func BenchmarkHazelcastSet(b *testing.B) {
ctx := context.Background()

client, err := hazelcast.StartNewClient(ctx)
hzClient, err := hazelcast.StartNewClient(ctx)
if err != nil {
b.Fatalf("Failed to start client: %v", err)
}

hzMap, err := client.GetMap(ctx, "gocache")
if err != nil {
b.Fatalf("Failed to get map: %v", err)
}

store := NewHazelcast(hzMap)
store := NewHazelcast(hzClient, "gocache")

for k := 0.; k <= 10; k++ {
n := int(math.Pow(2, k))
Expand All @@ -40,17 +35,12 @@ func BenchmarkHazelcastSet(b *testing.B) {
func BenchmarkHazelcastGet(b *testing.B) {
ctx := context.Background()

client, err := hazelcast.StartNewClient(ctx)
hzClient, err := hazelcast.StartNewClient(ctx)
if err != nil {
b.Fatalf("Failed to start client: %v", err)
}

hzMap, err := client.GetMap(ctx, "gocache")
if err != nil {
b.Fatalf("Failed to get map: %v", err)
}

store := NewHazelcast(hzMap)
store := NewHazelcast(hzClient, "gocache")

key := "test"
value := []byte("value")
Expand Down
26 changes: 12 additions & 14 deletions store/hazelcast/hazelcast_test.go
Expand Up @@ -13,16 +13,14 @@ import (

func TestNewHazelcast(t *testing.T) {
// Given
ctrl := gomock.NewController(t)

hzMap := NewMockHazelcastMapInterface(ctrl)
mapName := "gocache"

// When
store := NewHazelcast(hzMap, lib_store.WithExpiration(6*time.Second))
store := NewHazelcast(nil, mapName, lib_store.WithExpiration(6*time.Second))

// Then
assert.IsType(t, new(HazelcastStore), store)
assert.Equal(t, hzMap, store.hzMap)
assert.NotNil(t, store.mapProvider)
assert.Equal(t, &lib_store.Options{Expiration: 6 * time.Second}, store.options)
}

Expand All @@ -35,7 +33,7 @@ func TestHazelcastGet(t *testing.T) {
hzMap := NewMockHazelcastMapInterface(ctrl)
hzMap.EXPECT().Get(ctx, "my-key").Return("my-value", nil)

store := NewHazelcast(hzMap)
store := newHazelcast(hzMap)

// When
value, err := store.Get(ctx, "my-key")
Expand All @@ -57,7 +55,7 @@ func TestHazelcastSet(t *testing.T) {
hzMap := NewMockHazelcastMapInterface(ctrl)
hzMap.EXPECT().SetWithTTL(ctx, cacheKey, cacheValue, 5*time.Second).Return(nil)

store := NewHazelcast(hzMap, lib_store.WithExpiration(6*time.Second))
store := newHazelcast(hzMap, lib_store.WithExpiration(6*time.Second))

// When
err := store.Set(ctx, cacheKey, cacheValue, lib_store.WithExpiration(5*time.Second))
Expand All @@ -78,7 +76,7 @@ func TestHazelcastSetWhenNoOptionsGiven(t *testing.T) {
hzMap := NewMockHazelcastMapInterface(ctrl)
hzMap.EXPECT().SetWithTTL(ctx, cacheKey, cacheValue, 6*time.Second).Return(nil)

store := NewHazelcast(hzMap, lib_store.WithExpiration(6*time.Second))
store := newHazelcast(hzMap, lib_store.WithExpiration(6*time.Second))

// When
err := store.Set(ctx, cacheKey, cacheValue)
Expand All @@ -101,7 +99,7 @@ func TestHazelcastSetWithTags(t *testing.T) {
hzMap.EXPECT().SetWithTTL(gomock.Any(), "gocache_tag_tag1", cacheKey, TagKeyExpiry).Return(nil)
hzMap.EXPECT().Get(gomock.Any(), "gocache_tag_tag1").Return(nil, nil)

store := NewHazelcast(hzMap)
store := newHazelcast(hzMap)

// When
err := store.Set(ctx, cacheKey, cacheValue, lib_store.WithTags([]string{"tag1"}))
Expand All @@ -121,7 +119,7 @@ func TestHazelcastDelete(t *testing.T) {
hzMap := NewMockHazelcastMapInterface(ctrl)
hzMap.EXPECT().Remove(ctx, "my-key").Return(0, nil)

store := NewHazelcast(hzMap)
store := newHazelcast(hzMap)

// When
err := store.Delete(ctx, cacheKey)
Expand All @@ -139,7 +137,7 @@ func TestHazelcastInvalidate(t *testing.T) {
hzMap := NewMockHazelcastMapInterface(ctrl)
hzMap.EXPECT().Get(ctx, "gocache_tag_tag1").Return(nil, nil)

store := NewHazelcast(hzMap)
store := newHazelcast(hzMap)

// When
err := store.Invalidate(ctx, lib_store.WithInvalidateTags([]string{"tag1"}))
Expand All @@ -163,7 +161,7 @@ func TestHazelcastInvalidateWhenCacheKeysExist(t *testing.T) {
hzMap.EXPECT().Remove(ctx, "my-key2").Return("my-value2", nil)
hzMap.EXPECT().Remove(ctx, "gocache_tag_tag1").Return(cacheKeys, nil)

store := NewHazelcast(hzMap)
store := newHazelcast(hzMap)

// When
err := store.Invalidate(ctx, lib_store.WithInvalidateTags([]string{"tag1"}))
Expand All @@ -181,7 +179,7 @@ func TestHazelcastClear(t *testing.T) {
hzMap := NewMockHazelcastMapInterface(ctrl)
hzMap.EXPECT().Clear(ctx).Return(nil)

store := NewHazelcast(hzMap, lib_store.WithExpiration(6*time.Second))
store := newHazelcast(hzMap, lib_store.WithExpiration(6*time.Second))

// When
err := store.Clear(ctx)
Expand All @@ -196,7 +194,7 @@ func TestHazelcastType(t *testing.T) {

hzMap := NewMockHazelcastMapInterface(ctrl)

store := NewHazelcast(hzMap)
store := newHazelcast(hzMap)

// When - Then
assert.Equal(t, HazelcastType, store.GetType())
Expand Down

0 comments on commit 6d3bfb5

Please sign in to comment.