first commit
This commit is contained in:
+71
@@ -0,0 +1,71 @@
|
||||
// File: /home/meninjar/goprint/service-general/internal/infrastructure/cache/cache.go
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cache interface defines the contract for cache implementations
|
||||
type Cache interface {
|
||||
// Basic operations
|
||||
Get(ctx context.Context, key string) (string, error)
|
||||
Set(ctx context.Context, key string, value string, expiration time.Duration) error
|
||||
Delete(ctx context.Context, key string) error
|
||||
Exists(ctx context.Context, key string) (bool, error)
|
||||
|
||||
// Struct operations
|
||||
GetStruct(ctx context.Context, key string, dest interface{}) error
|
||||
SetStruct(ctx context.Context, key string, value interface{}, expiration time.Duration) error
|
||||
|
||||
// Numeric operations
|
||||
Increment(ctx context.Context, key string, delta int64) (int64, error)
|
||||
Decrement(ctx context.Context, key string, delta int64) (int64, error)
|
||||
|
||||
// Utility operations
|
||||
TTL(ctx context.Context, key string) (time.Duration, error)
|
||||
Close() error
|
||||
Health(ctx context.Context) error
|
||||
|
||||
// Batch operations
|
||||
MGet(ctx context.Context, keys ...string) ([]interface{}, error)
|
||||
MSet(ctx context.Context, items map[string]interface{}, expiration time.Duration) error
|
||||
}
|
||||
|
||||
// CacheConfig holds configuration for cache
|
||||
type CacheConfig struct {
|
||||
Enabled bool
|
||||
DefaultTTL time.Duration
|
||||
SessionTTL time.Duration
|
||||
RateLimitTTL time.Duration
|
||||
CleanupInterval time.Duration
|
||||
MaxRetries int
|
||||
RetryDelay time.Duration
|
||||
Redis RedisConfig
|
||||
}
|
||||
|
||||
type RedisConfig struct {
|
||||
Host string
|
||||
Port int
|
||||
Password string
|
||||
DB int
|
||||
}
|
||||
|
||||
// Common cache key prefixes
|
||||
const (
|
||||
KeyPrefixSession = "session:"
|
||||
KeyPrefixUser = "user:"
|
||||
KeyPrefixToken = "token:"
|
||||
KeyPrefixRateLimit = "rate_limit:"
|
||||
KeyPrefixCache = "cache:"
|
||||
KeyPrefixTemp = "temp:"
|
||||
)
|
||||
|
||||
// Common expiration times
|
||||
const (
|
||||
ExpirationSession = 24 * time.Hour
|
||||
ExpirationShort = 5 * time.Minute
|
||||
ExpirationMedium = 30 * time.Minute
|
||||
ExpirationLong = 2 * time.Hour
|
||||
ExpirationVeryLong = 24 * time.Hour
|
||||
)
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
// File: /home/meninjar/goprint/service-general/internal/infrastructure/cache/factory.go
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"service/internal/infrastructure/config"
|
||||
)
|
||||
|
||||
// Factory creates cache instances based on configuration
|
||||
type Factory struct {
|
||||
config config.CacheConfig
|
||||
}
|
||||
|
||||
// NewFactory creates a new cache factory
|
||||
func NewFactory(cfg config.CacheConfig) *Factory {
|
||||
return &Factory{
|
||||
config: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates a cache instance based on configuration
|
||||
func (f *Factory) Create() (Cache, error) {
|
||||
if !f.config.Enabled {
|
||||
return NewNoOpCache(), nil
|
||||
}
|
||||
|
||||
// For now, only Redis is supported
|
||||
return NewRedisCache(CacheConfig{
|
||||
Enabled: f.config.Enabled,
|
||||
DefaultTTL: f.config.DefaultTTL,
|
||||
SessionTTL: f.config.SessionTTL,
|
||||
RateLimitTTL: f.config.RateLimitTTL,
|
||||
CleanupInterval: f.config.CleanupInterval,
|
||||
MaxRetries: f.config.MaxRetries,
|
||||
RetryDelay: f.config.RetryDelay,
|
||||
Redis: RedisConfig{
|
||||
Host: f.config.Redis.Host,
|
||||
Port: f.config.Redis.Port,
|
||||
Password: f.config.Redis.Password,
|
||||
DB: f.config.Redis.DB,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// CreateManager creates a cache manager with the configured cache
|
||||
func (f *Factory) CreateManager() (*Manager, error) {
|
||||
cache, err := f.Create()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create cache: %w", err)
|
||||
}
|
||||
|
||||
return NewManager(cache, CacheConfig{
|
||||
Enabled: f.config.Enabled,
|
||||
DefaultTTL: f.config.DefaultTTL,
|
||||
SessionTTL: f.config.SessionTTL,
|
||||
RateLimitTTL: f.config.RateLimitTTL,
|
||||
CleanupInterval: f.config.CleanupInterval,
|
||||
MaxRetries: f.config.MaxRetries,
|
||||
RetryDelay: f.config.RetryDelay,
|
||||
Redis: RedisConfig{
|
||||
Host: f.config.Redis.Host,
|
||||
Port: f.config.Redis.Port,
|
||||
Password: f.config.Redis.Password,
|
||||
DB: f.config.Redis.DB,
|
||||
},
|
||||
}), nil
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Manager provides high-level cache operations
|
||||
type Manager struct {
|
||||
cache Cache
|
||||
config CacheConfig
|
||||
}
|
||||
|
||||
// RedisClientProvider interface untuk cache yang menyediakan Redis client
|
||||
type RedisClientProvider interface {
|
||||
GetRedisClient() interface{}
|
||||
}
|
||||
|
||||
// NewManager creates a new cache manager
|
||||
func NewManager(cache Cache, config CacheConfig) *Manager {
|
||||
return &Manager{
|
||||
cache: cache,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// Session operations
|
||||
func (m *Manager) SetSession(ctx context.Context, sessionID string, userData interface{}) error {
|
||||
key := KeyPrefixSession + sessionID
|
||||
return m.cache.SetStruct(ctx, key, userData, m.config.SessionTTL)
|
||||
}
|
||||
|
||||
func (m *Manager) GetSession(ctx context.Context, sessionID string, dest interface{}) error {
|
||||
key := KeyPrefixSession + sessionID
|
||||
return m.cache.GetStruct(ctx, key, dest)
|
||||
}
|
||||
|
||||
func (m *Manager) DeleteSession(ctx context.Context, sessionID string) error {
|
||||
key := KeyPrefixSession + sessionID
|
||||
return m.cache.Delete(ctx, key)
|
||||
}
|
||||
|
||||
// Rate limiting operations
|
||||
func (m *Manager) IncrementRateLimit(ctx context.Context, identifier string) (int64, error) {
|
||||
key := KeyPrefixRateLimit + identifier
|
||||
return m.cache.Increment(ctx, key, 1)
|
||||
}
|
||||
|
||||
func (m *Manager) GetRateLimit(ctx context.Context, identifier string) (int64, error) {
|
||||
key := KeyPrefixRateLimit + identifier
|
||||
// Try to get as string first, then convert
|
||||
value, err := m.cache.Get(ctx, key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var count int64
|
||||
_, err = fmt.Sscanf(value, "%d", &count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (m *Manager) ResetRateLimit(ctx context.Context, identifier string) error {
|
||||
key := KeyPrefixRateLimit + identifier
|
||||
return m.cache.Delete(ctx, key)
|
||||
}
|
||||
|
||||
func (m *Manager) SetRateLimit(ctx context.Context, identifier string, count int64) error {
|
||||
key := KeyPrefixRateLimit + identifier
|
||||
value := fmt.Sprintf("%d", count)
|
||||
return m.cache.Set(ctx, key, value, m.config.RateLimitTTL)
|
||||
}
|
||||
|
||||
// User cache operations
|
||||
func (m *Manager) SetUser(ctx context.Context, userID string, userData interface{}) error {
|
||||
key := KeyPrefixUser + userID
|
||||
return m.cache.SetStruct(ctx, key, userData, m.config.DefaultTTL)
|
||||
}
|
||||
|
||||
func (m *Manager) GetUser(ctx context.Context, userID string, dest interface{}) error {
|
||||
key := KeyPrefixUser + userID
|
||||
return m.cache.GetStruct(ctx, key, dest)
|
||||
}
|
||||
|
||||
func (m *Manager) DeleteUser(ctx context.Context, userID string) error {
|
||||
key := KeyPrefixUser + userID
|
||||
return m.cache.Delete(ctx, key)
|
||||
}
|
||||
|
||||
// Token operations
|
||||
func (m *Manager) SetToken(ctx context.Context, token string, tokenData interface{}, expiration time.Duration) error {
|
||||
key := KeyPrefixToken + token
|
||||
return m.cache.SetStruct(ctx, key, tokenData, expiration)
|
||||
}
|
||||
|
||||
func (m *Manager) GetToken(ctx context.Context, token string, dest interface{}) error {
|
||||
key := KeyPrefixToken + token
|
||||
return m.cache.GetStruct(ctx, key, dest)
|
||||
}
|
||||
|
||||
func (m *Manager) DeleteToken(ctx context.Context, token string) error {
|
||||
key := KeyPrefixToken + token
|
||||
return m.cache.Delete(ctx, key)
|
||||
}
|
||||
|
||||
// Generic cache operations with TTL
|
||||
func (m *Manager) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
|
||||
return m.cache.SetStruct(ctx, key, value, expiration)
|
||||
}
|
||||
|
||||
func (m *Manager) Get(ctx context.Context, key string, dest interface{}) error {
|
||||
return m.cache.GetStruct(ctx, key, dest)
|
||||
}
|
||||
|
||||
func (m *Manager) Delete(ctx context.Context, key string) error {
|
||||
return m.cache.Delete(ctx, key)
|
||||
}
|
||||
|
||||
func (m *Manager) Exists(ctx context.Context, key string) (bool, error) {
|
||||
return m.cache.Exists(ctx, key)
|
||||
}
|
||||
|
||||
// Health check
|
||||
func (m *Manager) Health(ctx context.Context) error {
|
||||
return m.cache.Health(ctx)
|
||||
}
|
||||
|
||||
// GetRedisClient mendapatkan Redis client jika tersedia
|
||||
func (m *Manager) GetRedisClient() interface{} {
|
||||
if redisProvider, ok := m.cache.(RedisClientProvider); ok {
|
||||
return redisProvider.GetRedisClient()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close the cache connection
|
||||
func (m *Manager) Close() error {
|
||||
return m.cache.Close()
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
// File: /home/meninjar/goprint/service-general/internal/infrastructure/cache/noop.go
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NoOpCache is a no-operation cache implementation for development
|
||||
type NoOpCache struct{}
|
||||
|
||||
// NewNoOpCache creates a new no-op cache instance
|
||||
func NewNoOpCache() Cache {
|
||||
return &NoOpCache{}
|
||||
}
|
||||
|
||||
// Get always returns empty string and error
|
||||
func (n *NoOpCache) Get(ctx context.Context, key string) (string, error) {
|
||||
return "", errors.New("cache disabled")
|
||||
}
|
||||
|
||||
// Set always succeeds but does nothing
|
||||
func (n *NoOpCache) Set(ctx context.Context, key string, value string, expiration time.Duration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete always succeeds but does nothing
|
||||
func (n *NoOpCache) Delete(ctx context.Context, key string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exists always returns false
|
||||
func (n *NoOpCache) Exists(ctx context.Context, key string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// GetStruct always returns error
|
||||
func (n *NoOpCache) GetStruct(ctx context.Context, key string, dest interface{}) error {
|
||||
return errors.New("cache disabled")
|
||||
}
|
||||
|
||||
// SetStruct always succeeds but does nothing
|
||||
func (n *NoOpCache) SetStruct(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Increment always returns 0 and error
|
||||
func (n *NoOpCache) Increment(ctx context.Context, key string, delta int64) (int64, error) {
|
||||
return 0, errors.New("cache disabled")
|
||||
}
|
||||
|
||||
// Decrement always returns 0 and error
|
||||
func (n *NoOpCache) Decrement(ctx context.Context, key string, delta int64) (int64, error) {
|
||||
return 0, errors.New("cache disabled")
|
||||
}
|
||||
|
||||
// TTL always returns 0 and error
|
||||
func (n *NoOpCache) TTL(ctx context.Context, key string) (time.Duration, error) {
|
||||
return 0, errors.New("cache disabled")
|
||||
}
|
||||
|
||||
// Close always succeeds
|
||||
func (n *NoOpCache) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Health always succeeds
|
||||
func (n *NoOpCache) Health(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MGet always returns empty slice and error
|
||||
func (n *NoOpCache) MGet(ctx context.Context, keys ...string) ([]interface{}, error) {
|
||||
return []interface{}{}, errors.New("cache disabled")
|
||||
}
|
||||
|
||||
// MSet always succeeds but does nothing
|
||||
func (n *NoOpCache) MSet(ctx context.Context, items map[string]interface{}, expiration time.Duration) error {
|
||||
return nil
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
// File: /home/meninjar/goprint/service-general/internal/infrastructure/cache/redis.go
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// RedisCache implements Cache interface using Redis
|
||||
type RedisCache struct {
|
||||
client *redis.Client
|
||||
config CacheConfig
|
||||
}
|
||||
|
||||
// NewRedisCache creates a new Redis cache instance
|
||||
func NewRedisCache(config CacheConfig) (Cache, error) {
|
||||
if !config.Enabled {
|
||||
return &NoOpCache{}, nil
|
||||
}
|
||||
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: fmt.Sprintf("%s:%d", config.Redis.Host, config.Redis.Port),
|
||||
Password: config.Redis.Password,
|
||||
DB: config.Redis.DB,
|
||||
})
|
||||
|
||||
// Test connection
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to Redis: %w", err)
|
||||
}
|
||||
|
||||
return &RedisCache{
|
||||
client: client,
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Get retrieves a value from cache
|
||||
func (r *RedisCache) Get(ctx context.Context, key string) (string, error) {
|
||||
return r.client.Get(ctx, key).Result()
|
||||
}
|
||||
|
||||
// Set stores a value in cache
|
||||
func (r *RedisCache) Set(ctx context.Context, key string, value string, expiration time.Duration) error {
|
||||
return r.client.Set(ctx, key, value, expiration).Err()
|
||||
}
|
||||
|
||||
// Delete removes a value from cache
|
||||
func (r *RedisCache) Delete(ctx context.Context, key string) error {
|
||||
return r.client.Del(ctx, key).Err()
|
||||
}
|
||||
|
||||
// Exists checks if a key exists in cache
|
||||
func (r *RedisCache) Exists(ctx context.Context, key string) (bool, error) {
|
||||
result, err := r.client.Exists(ctx, key).Result()
|
||||
return result > 0, err
|
||||
}
|
||||
|
||||
// GetStruct retrieves and unmarshals a struct from cache
|
||||
func (r *RedisCache) GetStruct(ctx context.Context, key string, dest interface{}) error {
|
||||
data, err := r.client.Get(ctx, key).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal([]byte(data), dest)
|
||||
}
|
||||
|
||||
// SetStruct marshals and stores a struct in cache
|
||||
func (r *RedisCache) SetStruct(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal value: %w", err)
|
||||
}
|
||||
return r.client.Set(ctx, key, data, expiration).Err()
|
||||
}
|
||||
|
||||
// Increment increments a numeric value in cache
|
||||
func (r *RedisCache) Increment(ctx context.Context, key string, delta int64) (int64, error) {
|
||||
return r.client.IncrBy(ctx, key, delta).Result()
|
||||
}
|
||||
|
||||
// Decrement decrements a numeric value in cache
|
||||
func (r *RedisCache) Decrement(ctx context.Context, key string, delta int64) (int64, error) {
|
||||
return r.client.DecrBy(ctx, key, delta).Result()
|
||||
}
|
||||
|
||||
// TTL returns the time to live for a key
|
||||
func (r *RedisCache) TTL(ctx context.Context, key string) (time.Duration, error) {
|
||||
return r.client.TTL(ctx, key).Result()
|
||||
}
|
||||
|
||||
// GetRedisClient mendapatkan Redis client
|
||||
func (r *RedisCache) GetRedisClient() interface{} {
|
||||
return r.client
|
||||
}
|
||||
|
||||
// Close closes the Redis connection
|
||||
func (r *RedisCache) Close() error {
|
||||
return r.client.Close()
|
||||
}
|
||||
|
||||
// Health checks the health of the cache
|
||||
func (r *RedisCache) Health(ctx context.Context) error {
|
||||
return r.client.Ping(ctx).Err()
|
||||
}
|
||||
|
||||
// MGet retrieves multiple values from cache
|
||||
func (r *RedisCache) MGet(ctx context.Context, keys ...string) ([]interface{}, error) {
|
||||
return r.client.MGet(ctx, keys...).Result()
|
||||
}
|
||||
|
||||
// MSet stores multiple values in cache
|
||||
func (r *RedisCache) MSet(ctx context.Context, items map[string]interface{}, expiration time.Duration) error {
|
||||
pipe := r.client.Pipeline()
|
||||
|
||||
for key, value := range items {
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal value for key %s: %w", key, err)
|
||||
}
|
||||
pipe.Set(ctx, key, data, expiration)
|
||||
}
|
||||
|
||||
_, err := pipe.Exec(ctx)
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user