Spaces:
Sleeping
Sleeping
| package xcache | |
| import ( | |
| "context" | |
| "errors" | |
| "fmt" | |
| "time" | |
| "github.com/eko/gocache/lib/v4/store" | |
| cachelib "github.com/eko/gocache/lib/v4/cache" | |
| gocache_store "github.com/eko/gocache/store/go_cache/v4" | |
| gocache "github.com/patrickmn/go-cache" | |
| redis "github.com/redis/go-redis/v9" | |
| "github.com/looplj/axonhub/internal/log" | |
| redis_store "github.com/looplj/axonhub/internal/pkg/xcache/redis" | |
| "github.com/looplj/axonhub/internal/pkg/xredis" | |
| ) | |
| // Cache is an alias to the gocache CacheInterface for convenience. | |
| // It allows you to depend on xcache while still exposing the common methods: | |
| // - Get(ctx, key) (T, error) | |
| // - Set(ctx, key, value, options ...Option) error | |
| // - Delete(ctx, key) error | |
| // - Invalidate(ctx, options ...store.InvalidateOption) error | |
| // - Clear(ctx) error | |
| // - GetType() string | |
| // For setter caches (memory/redis/chain), GetWithTTL and GetCodec are also available on the returned value. | |
| // See: github.com/eko/gocache/lib/v4/cache | |
| // | |
| // Usage example: | |
| // mem := xcache.NewMemory[string](gocache.New(5*time.Minute, 10*time.Minute)) | |
| // rdb := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"}) | |
| // rds := xcache.NewRedis[string](rdb, store.WithExpiration(15*time.Second)) | |
| // l2 := xcache.NewTwoLevel[string](mem, rds) | |
| // | |
| // _ = mem.Set(ctx, "foo", "bar") | |
| // val, _ := l2.Get(ctx, "foo") | |
| type Cache[T any] = cachelib.CacheInterface[T] | |
| type SetterCache[T any] = cachelib.SetterCacheInterface[T] | |
| // NewMemory creates a pure in-memory cache using patrickmn/go-cache as the backend. | |
| // Pass an existing *gocache.Cache so you control default expiration & cleanup interval. | |
| // Optionally pass store options (e.g., store.WithExpiration) when setting values. | |
| func NewMemory[T any](client *gocache.Cache, options ...Option) SetterCache[T] { | |
| store := gocache_store.NewGoCache(client, options...) | |
| return cachelib.New[T](store) | |
| } | |
| // NewMemoryWithOptions is a convenience constructor that builds the patrickmn/go-cache client | |
| // for you using the provided default expiration and cleanup interval. | |
| func NewMemoryWithOptions[T any](defaultExpiration, cleanupInterval time.Duration, options ...Option) SetterCache[T] { | |
| client := gocache.New(defaultExpiration, cleanupInterval) | |
| return NewMemory[T](client, options...) | |
| } | |
| // NewFromConfig builds a typed cache from the given Config. | |
| // Modes: | |
| // - memory: in-memory only | |
| // - redis: redis only | |
| // - two-level: memory + redis chain | |
| // | |
| // Memory and Redis expiration can be configured separately. | |
| // If mode is not set or invalid, returns a noop cache that does nothing. | |
| func NewFromConfig[T any](cfg Config) Cache[T] { | |
| // If mode is not set or empty, return noop cache | |
| if cfg.Mode == "" { | |
| return NewNoop[T]() | |
| } | |
| // Build memory setter cache with separate expiration settings | |
| memExpiration := defaultIfZero(cfg.Memory.Expiration, 5*time.Minute) | |
| memCleanupInterval := defaultIfZero(cfg.Memory.CleanupInterval, 10*time.Minute) | |
| memClient := gocache.New(memExpiration, memCleanupInterval) | |
| memStore := gocache_store.NewGoCache(memClient, store.WithExpiration(memExpiration)) | |
| mem := cachelib.New[T](memStore) | |
| // Build redis setter cache if configured | |
| var rds SetterCache[T] | |
| if (cfg.Redis.Addr != "" || cfg.Redis.URL != "") && cfg.Mode != ModeMemory { | |
| client, err := xredis.NewClient(cfg.Redis) | |
| if err != nil { | |
| panic(fmt.Errorf("invalid redis config: %w", err)) | |
| } | |
| redisExpiration := defaultIfZero(cfg.Redis.Expiration, 30*time.Minute) // Default longer for Redis | |
| rdsStore := redis_store.NewRedisStore[T](client, store.WithExpiration(redisExpiration)) | |
| rds = cachelib.New[T](rdsStore) | |
| } | |
| switch cfg.Mode { | |
| case ModeTwoLevel: | |
| if rds != nil { | |
| log.Info(context.Background(), "Using two-level cache") | |
| return cachelib.NewChain[T](mem, rds) | |
| } | |
| return mem | |
| case ModeRedis: | |
| if rds == nil { | |
| panic(errors.New("redis cache config is invalid")) | |
| } | |
| log.Info(context.Background(), "Using redis cache") | |
| return rds | |
| case ModeMemory: | |
| log.Info(context.Background(), "Using memory cache") | |
| return mem | |
| default: | |
| log.Info(context.Background(), "Disable cache") | |
| // Return noop cache for invalid modes | |
| return NewNoop[T]() | |
| } | |
| } | |
| func defaultIfZero(d, def time.Duration) time.Duration { | |
| if d == 0 { | |
| return def | |
| } | |
| return d | |
| } | |
| // NewRedis creates a pure Redis cache using github.com/redis/go-redis/v9 as the client. | |
| // You can pass store options like store.WithExpiration, store.WithTags, etc. | |
| func NewRedis[T any](client *redis.Client, options ...Option) SetterCache[T] { | |
| store := redis_store.NewRedisStore[T](client, options...) | |
| return cachelib.New[T](store) | |
| } | |
| // NewRedisWithOptions builds a redis.Client for you and returns the cache. | |
| func NewRedisWithOptions[T any](opts *redis.Options, options ...Option) SetterCache[T] { | |
| client := redis.NewClient(opts) | |
| return NewRedis[T](client, options...) | |
| } | |
| // NewTwoLevel constructs a 2-level cache: memory first, then Redis. | |
| // It takes already constructed setter caches so you can mix any memory/redis implementations. | |
| // Typical usage: NewTwoLevel(NewMemory(...), NewRedis(...)). | |
| func NewTwoLevel[T any](memory SetterCache[T], redis SetterCache[T]) Cache[T] { | |
| return cachelib.NewChain[T](memory, redis) | |
| } | |
| // NewTwoLevelWithClients is a convenience to create a 2-level cache from raw clients directly. | |
| func NewTwoLevelWithClients[T any](memClient *gocache.Cache, redisClient *redis.Client, memOptions []Option, redisOptions []Option) Cache[T] { | |
| mem := NewMemory[T](memClient, memOptions...) | |
| rds := NewRedis[T](redisClient, redisOptions...) | |
| return NewTwoLevel[T](mem, rds) | |
| } | |