|
| 1 | +// Package cache provides a flexible caching abstraction with multiple backend implementations. |
| 2 | +// |
| 3 | +// The cache package offers a unified interface for caching operations with support for: |
| 4 | +// - In-memory caching (memoryCache) |
| 5 | +// - Redis-based caching (redisCache) |
| 6 | +// - Type-safe caching through generics (Typed[T]) |
| 7 | +// |
| 8 | +// Features: |
| 9 | +// - Time-to-live (TTL) support for cache entries |
| 10 | +// - Atomic operations for consistency |
| 11 | +// - Concurrent-safe implementations |
| 12 | +// - Configurable expiration policies |
| 13 | +// - Support for custom serialization through the Item interface |
| 14 | +// |
| 15 | +// Basic Usage: |
| 16 | +// |
| 17 | +// // Create an in-memory cache with 1 hour default TTL |
| 18 | +// cache := cache.NewMemory(time.Hour) |
| 19 | +// |
| 20 | +// // Set a value |
| 21 | +// err := cache.Set(ctx, "key", []byte("value")) |
| 22 | +// if err != nil { |
| 23 | +// log.Fatal(err) |
| 24 | +// } |
| 25 | +// |
| 26 | +// // Get a value |
| 27 | +// value, err := cache.Get(ctx, "key") |
| 28 | +// if err != nil { |
| 29 | +// if errors.Is(err, cache.ErrKeyNotFound) { |
| 30 | +// // Handle missing key |
| 31 | +// } else if errors.Is(err, cache.ErrKeyExpired) { |
| 32 | +// // Handle expired key |
| 33 | +// } else { |
| 34 | +// log.Fatal(err) |
| 35 | +// } |
| 36 | +// } |
| 37 | +// |
| 38 | +// // Set with custom TTL |
| 39 | +// err = cache.Set(ctx, "key", []byte("value"), cache.WithTTL(30*time.Minute)) |
| 40 | +// |
| 41 | +// // Set only if key doesn't exist |
| 42 | +// err = cache.SetOrFail(ctx, "key", []byte("value")) |
| 43 | +// if errors.Is(err, cache.ErrKeyExists) { |
| 44 | +// // Key already exists |
| 45 | +// } |
| 46 | +// |
| 47 | +// // Get and delete in one operation |
| 48 | +// value, err = cache.GetAndDelete(ctx, "key") |
| 49 | +// |
| 50 | +// // Remove expired entries |
| 51 | +// err = cache.Cleanup(ctx) |
| 52 | +// |
| 53 | +// // Get all items and clear the cache |
| 54 | +// items, err := cache.Drain(ctx) |
| 55 | +// |
| 56 | +// // Close the cache when done |
| 57 | +// err = cache.Close() |
| 58 | +// |
| 59 | +// Using Typed Cache: |
| 60 | +// |
| 61 | +// // Define a type that implements the Item interface |
| 62 | +// type MyData struct { |
| 63 | +// Field1 string |
| 64 | +// Field2 int |
| 65 | +// } |
| 66 | +// |
| 67 | +// func (d *MyData) Marshal() ([]byte, error) { |
| 68 | +// return json.Marshal(d) |
| 69 | +// } |
| 70 | +// |
| 71 | +// func (d *MyData) Unmarshal(data []byte) error { |
| 72 | +// return json.Unmarshal(data, d) |
| 73 | +// } |
| 74 | +// |
| 75 | +// // Create a typed cache |
| 76 | +// storage := cache.NewMemory(time.Hour) |
| 77 | +// typedCache := cache.NewTyped[*MyData](storage) |
| 78 | +// |
| 79 | +// // Set typed value |
| 80 | +// data := &MyData{Field1: "test", Field2: 42} |
| 81 | +// err := typedCache.Set(ctx, "key", data) |
| 82 | +// |
| 83 | +// // Get typed value |
| 84 | +// retrieved, err := typedCache.Get(ctx, "key") |
| 85 | +// |
| 86 | +// Using Redis Cache: |
| 87 | +// |
| 88 | +// // Create a Redis cache |
| 89 | +// config := cache.RedisConfig{ |
| 90 | +// URL: "redis://localhost:6379", |
| 91 | +// Prefix: "myapp:", |
| 92 | +// TTL: time.Hour, |
| 93 | +// } |
| 94 | +// |
| 95 | +// redisCache, err := cache.NewRedis(config) |
| 96 | +// if err != nil { |
| 97 | +// log.Fatal(err) |
| 98 | +// } |
| 99 | +// defer redisCache.Close() |
| 100 | +// |
| 101 | +// // Use the same interface as memory cache |
| 102 | +// err = redisCache.Set(ctx, "key", []byte("value")) |
| 103 | +// value, err := redisCache.Get(ctx, "key") |
1 | 104 | package cache |
2 | 105 |
|
3 | 106 | import "context" |
4 | 107 |
|
| 108 | +// Cache defines the interface for cache implementations. |
| 109 | +// |
| 110 | +// All cache operations are context-aware and support cancellation and timeouts. |
| 111 | +// Implementations must be safe for concurrent use by multiple goroutines. |
5 | 112 | type Cache interface { |
6 | | - // Set sets the value for the given key in the cache. |
| 113 | + // Set stores the value for the given key in the cache, overwriting any existing value. |
| 114 | + // |
| 115 | + // The value will be stored with the default TTL configured for the cache implementation, |
| 116 | + // unless overridden by options. If the key already exists, its value and TTL will be updated. |
| 117 | + // |
| 118 | + // Parameters: |
| 119 | + // - ctx: Context for cancellation and timeouts |
| 120 | + // - key: The key to store the value under |
| 121 | + // - value: The value to store as a byte slice |
| 122 | + // - opts: Optional configuration for this specific item (e.g., custom TTL) |
| 123 | + // |
| 124 | + // Returns: |
| 125 | + // - error: nil on success, otherwise an error describing the failure |
| 126 | + // |
| 127 | + // Example: |
| 128 | + // // Set with default TTL |
| 129 | + // err := cache.Set(ctx, "user:123", []byte("user data")) |
| 130 | + // |
| 131 | + // // Set with custom TTL |
| 132 | + // err := cache.Set(ctx, "session:abc", []byte("session data"), cache.WithTTL(30*time.Minute)) |
| 133 | + // |
| 134 | + // // Set with specific expiration time |
| 135 | + // expiration := time.Now().Add(2 * time.Hour) |
| 136 | + // err := cache.Set(ctx, "temp:xyz", []byte("temp data"), cache.WithValidUntil(expiration)) |
7 | 137 | Set(ctx context.Context, key string, value []byte, opts ...Option) error |
8 | 138 |
|
9 | | - // SetOrFail is like Set, but returns ErrKeyExists if the key already exists. |
| 139 | + // SetOrFail stores the value for the given key only if the key does not already exist. |
| 140 | + // |
| 141 | + // This is an atomic operation that prevents race conditions when multiple goroutines |
| 142 | + // might try to set the same key simultaneously. If the key exists but has expired, |
| 143 | + // it will be overwritten. |
| 144 | + // |
| 145 | + // Parameters: |
| 146 | + // - ctx: Context for cancellation and timeouts |
| 147 | + // - key: The key to store the value under |
| 148 | + // - value: The value to store as a byte slice |
| 149 | + // - opts: Optional configuration for this specific item (e.g., custom TTL) |
| 150 | + // |
| 151 | + // Returns: |
| 152 | + // - error: nil on success, ErrKeyExists if the key already exists and is not expired, |
| 153 | + // otherwise an error describing the failure |
| 154 | + // |
| 155 | + // Example: |
| 156 | + // // Try to set a value only if key doesn't exist |
| 157 | + // err := cache.SetOrFail(ctx, "lock:resource", []byte("locked")) |
| 158 | + // if errors.Is(err, cache.ErrKeyExists) { |
| 159 | + // // Key already exists, handle conflict |
| 160 | + // } |
10 | 161 | SetOrFail(ctx context.Context, key string, value []byte, opts ...Option) error |
11 | 162 |
|
12 | | - // Get gets the value for the given key from the cache. |
| 163 | + // Get retrieves the value for the given key from the cache. |
| 164 | + // |
| 165 | + // The behavior depends on the key's existence and expiration state: |
| 166 | + // - If the key exists and has not expired, returns the value and nil error |
| 167 | + // - If the key does not exist, returns nil and ErrKeyNotFound |
| 168 | + // - If the key exists but has expired, returns nil and ErrKeyExpired |
| 169 | + // |
| 170 | + // GetOptions can be used to modify the behavior, such as updating TTL, |
| 171 | + // deleting the key after retrieval, or setting a new expiration time. |
| 172 | + // |
| 173 | + // Parameters: |
| 174 | + // - ctx: Context for cancellation and timeouts |
| 175 | + // - key: The key to retrieve |
| 176 | + // - opts: Optional operations to perform during retrieval (e.g., AndDelete, AndSetTTL) |
| 177 | + // |
| 178 | + // Returns: |
| 179 | + // - []byte: The cached value if found and not expired |
| 180 | + // - error: nil on success, ErrKeyNotFound if key doesn't exist, |
| 181 | + // ErrKeyExpired if key exists but has expired, otherwise an error |
13 | 182 | // |
14 | | - // If the key is not found, it returns ErrKeyNotFound. |
15 | | - // If the key has expired, it returns ErrKeyExpired. |
16 | | - // Otherwise, it returns the value and nil. |
| 183 | + // Example: |
| 184 | + // // Simple get |
| 185 | + // value, err := cache.Get(ctx, "user:123") |
| 186 | + // |
| 187 | + // // Get and extend TTL by 30 minutes |
| 188 | + // value, err := cache.Get(ctx, "session:abc", cache.AndUpdateTTL(30*time.Minute)) |
| 189 | + // |
| 190 | + // // Get and delete atomically |
| 191 | + // value, err := cache.Get(ctx, "temp:xyz", cache.AndDelete()) |
17 | 192 | Get(ctx context.Context, key string, opts ...GetOption) ([]byte, error) |
18 | 193 |
|
19 | | - // GetAndDelete is like Get, but also deletes the key from the cache. |
| 194 | + // GetAndDelete retrieves the value for the given key and atomically deletes it from the cache. |
| 195 | + // |
| 196 | + // This is equivalent to calling Get with the AndDelete option, but provides a more |
| 197 | + // convenient API for the common pattern of reading and removing a value in one operation. |
| 198 | + // |
| 199 | + // Parameters: |
| 200 | + // - ctx: Context for cancellation and timeouts |
| 201 | + // - key: The key to retrieve and delete |
| 202 | + // |
| 203 | + // Returns: |
| 204 | + // - []byte: The cached value if found and not expired |
| 205 | + // - error: nil on success, ErrKeyNotFound if key doesn't exist, |
| 206 | + // ErrKeyExpired if key exists but has expired, otherwise an error |
| 207 | + // |
| 208 | + // Example: |
| 209 | + // // Atomically get and remove a value |
| 210 | + // value, err := cache.GetAndDelete(ctx, "queue:item:123") |
20 | 211 | GetAndDelete(ctx context.Context, key string) ([]byte, error) |
21 | 212 |
|
22 | 213 | // Delete removes the item associated with the given key from the cache. |
23 | | - // If the key does not exist, it performs no action and returns nil. |
24 | | - // The operation is safe for concurrent use. |
| 214 | + // |
| 215 | + // If the key does not exist, this operation performs no action and returns nil. |
| 216 | + // The operation is safe for concurrent use by multiple goroutines. |
| 217 | + // |
| 218 | + // Parameters: |
| 219 | + // - ctx: Context for cancellation and timeouts |
| 220 | + // - key: The key to remove from the cache |
| 221 | + // |
| 222 | + // Returns: |
| 223 | + // - error: nil on success, otherwise an error describing the failure |
| 224 | + // |
| 225 | + // Example: |
| 226 | + // // Remove a specific key |
| 227 | + // err := cache.Delete(ctx, "user:123") |
25 | 228 | Delete(ctx context.Context, key string) error |
26 | 229 |
|
27 | 230 | // Cleanup removes all expired items from the cache. |
28 | | - // The operation is safe for concurrent use. |
| 231 | + // |
| 232 | + // This operation scans the entire cache and removes any items that have expired. |
| 233 | + // The operation is safe for concurrent use by multiple goroutines. |
| 234 | + // Note that some cache implementations (like Redis) handle expiration automatically |
| 235 | + // and may not require explicit cleanup. |
| 236 | + // |
| 237 | + // Parameters: |
| 238 | + // - ctx: Context for cancellation and timeouts |
| 239 | + // |
| 240 | + // Returns: |
| 241 | + // - error: nil on success, otherwise an error describing the failure |
| 242 | + // |
| 243 | + // Example: |
| 244 | + // // Periodically clean up expired items |
| 245 | + // err := cache.Cleanup(ctx) |
29 | 246 | Cleanup(ctx context.Context) error |
30 | 247 |
|
31 | | - // Drain returns a map of all the non-expired items in the cache. |
| 248 | + // Drain returns a map of all non-expired items in the cache and clears the cache. |
| 249 | + // |
32 | 250 | // The returned map is a snapshot of the cache at the time of the call. |
33 | | - // The cache is cleared after the call. |
34 | | - // The operation is safe for concurrent use. |
| 251 | + // After this operation, the cache will be empty. This is useful for cache migration, |
| 252 | + // backup, or when shutting down an application. |
| 253 | + // The operation is safe for concurrent use by multiple goroutines. |
| 254 | + // |
| 255 | + // Parameters: |
| 256 | + // - ctx: Context for cancellation and timeouts |
| 257 | + // |
| 258 | + // Returns: |
| 259 | + // - map[string][]byte: A map containing all non-expired key-value pairs |
| 260 | + // - error: nil on success, otherwise an error describing the failure |
| 261 | + // |
| 262 | + // Example: |
| 263 | + // // Get all items and clear the cache |
| 264 | + // items, err := cache.Drain(ctx) |
| 265 | + // for key, value := range items { |
| 266 | + // log.Printf("Drained: %s = %s", key, string(value)) |
| 267 | + // } |
35 | 268 | Drain(ctx context.Context) (map[string][]byte, error) |
36 | 269 |
|
37 | | - // Close closes the cache. |
38 | | - // The operation is safe for concurrent use. |
| 270 | + // Close releases any resources held by the cache. |
| 271 | + // |
| 272 | + // This should be called when the cache is no longer needed. For some implementations |
| 273 | + // (like Redis), this may close network connections. The operation is safe for |
| 274 | + // concurrent use by multiple goroutines. |
| 275 | + // |
| 276 | + // Returns: |
| 277 | + // - error: nil on success, otherwise an error describing the failure |
| 278 | + // |
| 279 | + // Example: |
| 280 | + // // Properly close the cache when done |
| 281 | + // defer cache.Close() |
39 | 282 | Close() error |
40 | 283 | } |
0 commit comments