diff --git a/apps/api-server/internal/cache/cache.go b/apps/api-server/internal/cache/cache.go new file mode 100644 index 0000000..1bed574 --- /dev/null +++ b/apps/api-server/internal/cache/cache.go @@ -0,0 +1,412 @@ +package cache + +import ( + "encoding/json" + "fmt" + "sync" + "time" +) + +// CacheItem represents an item in the cache +type CacheItem struct { + Key string `json:"key"` + Value any `json:"value"` + Expiration time.Time `json:"expiration"` + CreatedAt time.Time `json:"created_at"` + AccessCount int64 `json:"access_count"` + LastAccess time.Time `json:"last_access"` +} + +// IsExpired checks if the cache item has expired +func (ci *CacheItem) IsExpired() bool { + return time.Now().After(ci.Expiration) +} + +// Cache interface defines the cache operations +type Cache interface { + Get(key string) (any, bool) + Set(key string, value any, ttl time.Duration) + Delete(key string) + Clear() + Keys() []string + Size() int + Stats() CacheStats +} + +// CacheStats provides cache statistics +type CacheStats struct { + Hits int64 `json:"hits"` + Misses int64 `json:"misses"` + HitRate float64 `json:"hit_rate"` + Size int `json:"size"` + Evictions int64 `json:"evictions"` + TotalItems int64 `json:"total_items"` +} + +// MemoryCache is an in-memory cache implementation +type MemoryCache struct { + items map[string]*CacheItem + mu sync.RWMutex + stats CacheStats + maxSize int + cleanupTick *time.Ticker + stopCleanup chan bool +} + +// NewMemoryCache creates a new memory cache +func NewMemoryCache(maxSize int, cleanupInterval time.Duration) *MemoryCache { + cache := &MemoryCache{ + items: make(map[string]*CacheItem), + maxSize: maxSize, + cleanupTick: time.NewTicker(cleanupInterval), + stopCleanup: make(chan bool), + } + + // Start cleanup goroutine + go cache.cleanup() + + return cache +} + +// cleanup removes expired items from cache +func (mc *MemoryCache) cleanup() { + for { + select { + case <-mc.cleanupTick.C: + mc.removeExpired() + case <-mc.stopCleanup: + return + } + } +} + +// removeExpired removes expired items from cache +func (mc *MemoryCache) removeExpired() { + mc.mu.Lock() + defer mc.mu.Unlock() + + now := time.Now() + for key, item := range mc.items { + if now.After(item.Expiration) { + delete(mc.items, key) + mc.stats.Evictions++ + } + } +} + +// Get retrieves an item from cache +func (mc *MemoryCache) Get(key string) (any, bool) { + mc.mu.RLock() + defer mc.mu.RUnlock() + + item, exists := mc.items[key] + if !exists { + mc.stats.Misses++ + return nil, false + } + + if item.IsExpired() { + delete(mc.items, key) + mc.stats.Misses++ + mc.stats.Evictions++ + return nil, false + } + + // Update access statistics + item.AccessCount++ + item.LastAccess = time.Now() + mc.stats.Hits++ + + return item.Value, true +} + +// Set stores an item in cache +func (mc *MemoryCache) Set(key string, value any, ttl time.Duration) { + mc.mu.Lock() + defer mc.mu.Unlock() + + // Check if we need to evict items to make space + if len(mc.items) >= mc.maxSize { + mc.evictLRU() + } + + expiration := time.Now().Add(ttl) + item := &CacheItem{ + Key: key, + Value: value, + Expiration: expiration, + CreatedAt: time.Now(), + AccessCount: 1, + LastAccess: time.Now(), + } + + mc.items[key] = item + mc.stats.TotalItems++ +} + +// evictLRU removes the least recently used item +func (mc *MemoryCache) evictLRU() { + var lruKey string + var lruTime time.Time + + for key, item := range mc.items { + if lruKey == "" || item.LastAccess.Before(lruTime) { + lruKey = key + lruTime = item.LastAccess + } + } + + if lruKey != "" { + delete(mc.items, lruKey) + mc.stats.Evictions++ + } +} + +// Delete removes an item from cache +func (mc *MemoryCache) Delete(key string) { + mc.mu.Lock() + defer mc.mu.Unlock() + + delete(mc.items, key) +} + +// Clear removes all items from cache +func (mc *MemoryCache) Clear() { + mc.mu.Lock() + defer mc.mu.Unlock() + + mc.items = make(map[string]*CacheItem) + mc.stats.Evictions += int64(len(mc.items)) +} + +// Keys returns all keys in cache +func (mc *MemoryCache) Keys() []string { + mc.mu.RLock() + defer mc.mu.RUnlock() + + keys := make([]string, 0, len(mc.items)) + for key := range mc.items { + keys = append(keys, key) + } + return keys +} + +// Size returns the current cache size +func (mc *MemoryCache) Size() int { + mc.mu.RLock() + defer mc.mu.RUnlock() + + return len(mc.items) +} + +// Stats returns cache statistics +func (mc *MemoryCache) Stats() CacheStats { + mc.mu.RLock() + defer mc.mu.RUnlock() + + total := mc.stats.Hits + mc.stats.Misses + hitRate := 0.0 + if total > 0 { + hitRate = float64(mc.stats.Hits) / float64(total) * 100 + } + + return CacheStats{ + Hits: mc.stats.Hits, + Misses: mc.stats.Misses, + HitRate: hitRate, + Size: len(mc.items), + Evictions: mc.stats.Evictions, + TotalItems: mc.stats.TotalItems, + } +} + +// Close stops the cache cleanup goroutine +func (mc *MemoryCache) Close() { + mc.cleanupTick.Stop() + close(mc.stopCleanup) +} + +// RedisCache is a Redis-based cache implementation (placeholder) +type RedisCache struct { + // Redis client would be here +} + +// NewRedisCache creates a new Redis cache +func NewRedisCache(addr string, password string, db int) *RedisCache { + // Implementation would connect to Redis + return &RedisCache{} +} + +// Get retrieves an item from Redis cache +func (rc *RedisCache) Get(key string) (any, bool) { + // Redis GET implementation + return nil, false +} + +// Set stores an item in Redis cache +func (rc *RedisCache) Set(key string, value any, ttl time.Duration) { + // Redis SET implementation +} + +// Delete removes an item from Redis cache +func (rc *RedisCache) Delete(key string) { + // Redis DEL implementation +} + +// Clear removes all items from Redis cache +func (rc *RedisCache) Clear() { + // Redis FLUSH implementation +} + +// Keys returns all keys in Redis cache +func (rc *RedisCache) Keys() []string { + // Redis KEYS implementation + return nil +} + +// Size returns the current cache size +func (rc *RedisCache) Size() int { + // Redis DBSIZE implementation + return 0 +} + +// Stats returns cache statistics +func (rc *RedisCache) Stats() CacheStats { + // Redis INFO implementation + return CacheStats{} +} + +// CacheManager manages multiple cache instances +type CacheManager struct { + caches map[string]Cache + mu sync.RWMutex +} + +// NewCacheManager creates a new cache manager +func NewCacheManager() *CacheManager { + return &CacheManager{ + caches: make(map[string]Cache), + } +} + +// AddCache adds a cache instance +func (cm *CacheManager) AddCache(name string, cache Cache) { + cm.mu.Lock() + defer cm.mu.Unlock() + cm.caches[name] = cache +} + +// GetCache retrieves a cache instance +func (cm *CacheManager) GetCache(name string) (Cache, bool) { + cm.mu.RLock() + defer cm.mu.RUnlock() + cache, exists := cm.caches[name] + return cache, exists +} + +// RemoveCache removes a cache instance +func (cm *CacheManager) RemoveCache(name string) { + cm.mu.Lock() + defer cm.mu.Unlock() + delete(cm.caches, name) +} + +// GetAllStats returns statistics for all caches +func (cm *CacheManager) GetAllStats() map[string]CacheStats { + cm.mu.RLock() + defer cm.mu.RUnlock() + + stats := make(map[string]CacheStats) + for name, cache := range cm.caches { + stats[name] = cache.Stats() + } + return stats +} + +// ClearAll clears all caches +func (cm *CacheManager) ClearAll() { + cm.mu.RLock() + defer cm.mu.RUnlock() + + for _, cache := range cm.caches { + cache.Clear() + } +} + +// Global cache manager instance +var globalCacheManager *CacheManager + +// InitializeCacheManager initializes the global cache manager +func InitializeCacheManager() { + globalCacheManager = NewCacheManager() + + // Add default memory cache + memoryCache := NewMemoryCache(1000, 5*time.Minute) + globalCacheManager.AddCache("default", memoryCache) + + // Add specialized caches + userCache := NewMemoryCache(500, 10*time.Minute) + globalCacheManager.AddCache("users", userCache) + + serviceCache := NewMemoryCache(200, 2*time.Minute) + globalCacheManager.AddCache("services", serviceCache) + + metricsCache := NewMemoryCache(1000, 1*time.Minute) + globalCacheManager.AddCache("metrics", metricsCache) +} + +// GetCacheManager returns the global cache manager +func GetCacheManager() *CacheManager { + return globalCacheManager +} + +// GetDefaultCache returns the default cache +func GetDefaultCache() Cache { + if globalCacheManager == nil { + InitializeCacheManager() + } + cache, _ := globalCacheManager.GetCache("default") + return cache +} + +// GetUserCache returns the user cache +func GetUserCache() Cache { + if globalCacheManager == nil { + InitializeCacheManager() + } + cache, _ := globalCacheManager.GetCache("users") + return cache +} + +// GetServiceCache returns the service cache +func GetServiceCache() Cache { + if globalCacheManager == nil { + InitializeCacheManager() + } + cache, _ := globalCacheManager.GetCache("services") + return cache +} + +// GetMetricsCache returns the metrics cache +func GetMetricsCache() Cache { + if globalCacheManager == nil { + InitializeCacheManager() + } + cache, _ := globalCacheManager.GetCache("metrics") + return cache +} + +// CacheKey generates a cache key with namespace +func CacheKey(namespace, key string) string { + return fmt.Sprintf("%s:%s", namespace, key) +} + +// SerializeValue serializes a value for caching +func SerializeValue(value any) ([]byte, error) { + return json.Marshal(value) +} + +// DeserializeValue deserializes a value from cache +func DeserializeValue(data []byte, target any) error { + return json.Unmarshal(data, target) +}