-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtypedcache.go
384 lines (356 loc) · 13.5 KB
/
typedcache.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
package resilicache
import (
"context"
"encoding/json"
"time"
)
// TypedCache is a generic wrapper around the Cache struct, allowing strongly-typed cache operations.
// It provides a type-safe way to store and retrieve values in the cache by leveraging Go's generics.
//
// This wrapper simplifies the process of storing and retrieving typed values by handling JSON serialization
// and deserialization internally. It delegates the actual caching operations to an underlying Cache instance.
//
// Type Parameters:
// - T: The type of the values that will be cached.
type TypedCache[T any] struct {
cache *Cache
}
// NewTypedCache creates a new instance of TypedCache for the specified type `T`.
// It initializes the TypedCache with the provided Cache instance, allowing for type-safe caching operations.
//
// Parameters:
// - cache: A pointer to the Cache instance that will handle the underlying cache operations.
//
// Type Parameters:
// - T: The type of the values that will be cached.
//
// Returns:
// - *TypedCache[T]: A pointer to the newly created TypedCache instance.
//
// This function is typically used to create a TypedCache for a specific type:
// Example:
//
// cache := NewCache(redisClient, opts)
// typedCache := NewTypedCache[string](cache)
func NewTypedCache[T any](cache *Cache) *TypedCache[T] {
return &TypedCache[T]{
cache: cache,
}
}
// Set stores a typed value in the cache under the specified key, with an associated expiration time.
// This method serializes the value to JSON before storing.
//
// Parameters:
// - ctx: The context to control cancellation and timeouts.
// - key: The unique identifier for the cache entry (e.g., cache key).
// - value: The typed value to be stored in the cache, which will be serialized to JSON.
// - expire: The duration after which the cache entry will expire and be automatically deleted.
//
// Returns:
// - error: If the serialization or cache operation fails, an error is returned. Otherwise, it returns nil.
//
// Example:
// To store a string value in the cache:
//
// err := Set(ctx, "user_01J4YHWG45SC7VW684TZB2SZ7K", "example_value", 10*time.Minute)
// if err != nil {
// log.Fatalf("Failed to set cache: %v", err)
// }
func (tc *TypedCache[T]) Set(ctx context.Context, key string, value T, expire time.Duration) error {
data, err := json.Marshal(value)
if err != nil {
return err
}
return tc.cache.Set(ctx, key, data, expire)
}
// Get retrieves a typed value from the cache using the specified key.
// This method deserializes the value to defined type on retrieval.
//
// Parameters:
// - ctx: The context to control cancellation and timeouts.
// - key: The unique identifier for the cache entry (e.g., cache key).
//
// Returns:
// - T: The typed value retrieved from the cache. If the key does not exist, a zero value for the type `T` is returned.
// - error: If the cache operation or deserialization fails, an error is returned. Otherwise, it returns nil.
//
// Example:
// To retrieve a string value from the cache:
//
// value, err := Get(ctx, "user_01J4YHWG45SC7VW684TZB2SZ7K")
// if err != nil {
// log.Fatalf("Failed to get cache: %v", err)
// }
// log.Printf("Fetched value: %s", value)
func (tc *TypedCache[T]) Get(ctx context.Context, key string) (T, error) {
var value T
dataBytes, err := tc.cache.Get(ctx, key)
if err != nil {
return value, err
}
if dataBytes == nil {
return value, nil
}
err = json.Unmarshal(dataBytes, &value)
return value, err
}
// Delete removes a typed value from the cache using the specified key.
//
// Parameters:
// - ctx: The context to control cancellation and timeouts.
// - key: The unique identifier for the cache entry to be deleted (e.g., cache key).
//
// Returns:
// - error: If the cache operation fails, an error is returned. Otherwise, it returns nil.
//
// Example:
// To delete a value from the cache:
//
// err := Delete(ctx, "user_01J4YHWG45SC7VW684TZB2SZ7K")
// if err != nil {
// log.Fatalf("Failed to delete cache key: %v", err)
// }
func (tc *TypedCache[T]) Delete(ctx context.Context, key string) error {
return tc.cache.Delete(ctx, key)
}
// FetchSingle retrieves a typed value from the cache using the specified key.
// If the key is not found, the provided function `fn` is called to generate the value, which is then stored in the cache then returned.
// This method deserializes the value to defined type on retrieval.
//
// Parameters:
// - ctx: The context to control cancellation and timeouts.
// - key: The unique identifier for the cache entry.
// - expire: The duration after which the cache entry will expire.
// - fn: A function that returns the value to be cached if it is not already present.
//
// Returns:
// - T: The typed value retrieved from or stored in the cache.
// - error: If the cache operation, serialization, or deserialization fails, an error is returned.
//
// Example:
// To fetch or generate a string value:
//
// value, err := FetchSingle(ctx, "user_01J4YHWG45SC7VW684TZB2SZ7K", 10*time.Minute, func() (string, error) {
// return "generated_value", nil
// })
// if err != nil {
// log.Fatalf("Failed to fetch cache: %v", err)
// }
// log.Printf("Fetched or generated value: %s", value)
func (tc *TypedCache[T]) FetchSingle(ctx context.Context, key string, expire time.Duration, fn func() (T, error)) (T, error) {
var value T
fetchSingleFn := func() ([]byte, error) {
result, err := fn()
if err != nil {
return nil, err
}
return json.Marshal(result)
}
dataBytes, err := tc.cache.FetchSingle(ctx, key, expire, fetchSingleFn)
if err != nil {
return value, err
}
err = json.Unmarshal(dataBytes, &value)
return value, err
}
// TagAsDeletedSingle marks a single cache entry as deleted for the given key.
// This method does not remove the entry immediately but tags it as deleted for delayed deletion.
//
// Parameters:
// - ctx: The context to control cancellation and timeouts.
// - key: The unique identifier for the cache entry to be marked as deleted.
//
// Returns:
// - error: If the cache operation fails, an error is returned.
//
// Example:
// To mark a cache entry as deleted:
//
// err := TagAsDeletedSingle(ctx, "user_01J4YHWG45SC7VW684TZB2SZ7K")
// if err != nil {
// log.Fatalf("Failed to mark cache key as deleted: %v", err)
// }
func (tc *TypedCache[T]) TagAsDeletedSingle(ctx context.Context, key string) error {
return tc.cache.TagAsDeletedSingle(ctx, key)
}
// FetchBatch retrieves multiple typed values from the cache using the specified keys.
// If some keys are not found, the provided function `fn` is called to generate the missing values, which are then stored in the cache then returned.
// This method deserializes the values to defined type on retrieval.
//
// Parameters:
// - ctx: The context to control cancellation and timeouts.
// - keys: A slice of unique identifiers for the cache entries.
// - expire: The duration after which the cache entries will expire.
// - fn: A function that returns a map of missing index positions to their corresponding values to be cached.
//
// Returns:
// - map[int]T: A map of index positions to the typed values retrieved from or stored in the cache.
// - error: If the cache operation, serialization, or deserialization fails, an error is returned.
//
// Example:
// To fetch or generate multiple string values:
//
// values, err := FetchBatch(ctx, []string{"key1", "key2"}, 10*time.Minute, func(idxs []int) (map[int]string, error) {
// return map[int]string{
// indexes[0]: "generated_value1",
// indexes[1]: "generated_value2",
// }, nil
// })
// if err != nil {
// log.Fatalf("Failed to fetch batch cache: %v", err)
// }
// log.Printf("Fetched or generated values: %v", values)
func (tc *TypedCache[T]) FetchBatch(ctx context.Context, keys []string, expire time.Duration, fn func(idxs []int) (map[int]T, error)) (map[int]T, error) {
fetchBatchFn := func(indexes []int) (map[int][]byte, error) {
result, err := fn(indexes)
if err != nil {
return nil, err
}
byteResult := make(map[int][]byte)
for idx, val := range result {
data, err := json.Marshal(val)
if err != nil {
return nil, err
}
byteResult[idx] = data
}
return byteResult, nil
}
dataBytes, err := tc.cache.FetchBatch(ctx, keys, expire, fetchBatchFn)
if err != nil {
return nil, err
}
values := make(map[int]T)
for idx, data := range dataBytes {
var value T
if err := json.Unmarshal(data, &value); err != nil {
continue
}
values[idx] = value
}
return values, nil
}
// TagAsDeletedBatch marks multiple cache entries as deleted for the given keys.
// This method does not remove the entries immediately but tags them as deleted for delayed deletion.
//
// Parameters:
// - ctx: The context to control cancellation and timeouts.
// - keys: A slice of unique identifiers for the cache entries to be marked as deleted.
//
// Returns:
// - error: If the cache operation fails, an error is returned.
//
// Example:
// To mark multiple cache entries as deleted:
//
// err := TagAsDeletedBatch(ctx, []string{"key1", "key2"})
// if err != nil {
// log.Fatalf("Failed to mark cache keys as deleted: %v", err)
// }
func (tc *TypedCache[T]) TagAsDeletedBatch(ctx context.Context, keys []string) error {
return tc.cache.TagAsDeletedBatch(ctx, keys)
}
// RawSet stores a typed value in the cache under the specified key, with an associated expiration time.
// This method directly serializes the value to JSON and stores it using the underlying Cache instance's RawSet method.
//
// Warning:
// Use caution when using `RawSet`, as it does not respect any locks that may be in place on the cache entry.
// This could lead to race conditions or overwriting data that is currently being updated by another process.
//
// Parameters:
// - ctx: The context to control cancellation and timeouts.
// - key: The unique identifier for the cache entry.
// - value: The typed value to be stored in the cache, which will be serialized to JSON.
// - expire: The duration after which the cache entry will expire and be automatically deleted.
//
// Returns:
// - error: If the serialization or cache operation fails, an error is returned.
//
// Example:
// To directly store a string value in the cache:
//
// err := RawSet(ctx, "user_01J4YHWG45SC7VW684TZB2SZ7K", "example_value", 10*time.Minute)
// if err != nil {
// log.Fatalf("Failed to set cache: %v", err)
// }
func (tc *TypedCache[T]) RawSet(ctx context.Context, key string, value T, expire time.Duration) error {
data, err := json.Marshal(value)
if err != nil {
return err
}
return tc.cache.RawSet(ctx, key, data, expire)
}
// RawGet retrieves a typed value from the cache using the specified key, without any additional logic.
// This method deserializes the values to defined type on retrieval.
//
// Warning:
// Use caution when using `RawGet`, as it does not respect any locks that may be in place on the cache entry.
// This could lead to reading stale or inconsistent data if another process is currently updating the value.
//
// Parameters:
// - ctx: The context to control cancellation and timeouts.
// - key: The unique identifier for the cache entry.
//
// Returns:
// - T: The typed value retrieved from the cache. If the key does not exist, a zero value for the type `T` is returned.
// - error: If the cache operation or deserialization fails, an error is returned.
//
// Example:
// To directly retrieve a string value:
//
// value, err := RawGet(ctx, "user_01J4YHWG45SC7VW684TZB2SZ7K")
// if err != nil {
// log.Fatalf("Failed to get cache: %v", err)
// }
// log.Printf("Fetched value: %s", value)
func (tc *TypedCache[T]) RawGet(ctx context.Context, key string) (T, error) {
var value T
data, err := tc.cache.RawGet(ctx, key)
if err != nil {
return value, err
}
err = json.Unmarshal(data, &value)
return value, err
}
// LockForUpdate locks a cache entry for update, ensuring that only one operation can modify the entry at a time.
// The lock is associated with a specific owner, which must be provided to unlock the entry later.
//
// Parameters:
// - ctx: The context to control cancellation and timeouts.
// - key: The unique identifier for the cache entry to be locked.
// - owner: The owner of the lock, used to identify the locking operation.
//
// Returns:
// - error: If the cache operation fails, an error is returned.
//
// Example:
// To lock a cache entry for update:
//
// err := LockForUpdate(ctx, "user_01J4YHWG45SC7VW684TZB2SZ7K", "service_01J4ZXVMKDFTSZGYKT5FXAZAB4")
// if err != nil {
// log.Fatalf("Failed to lock cache key for update: %v", err)
// }
func (tc *TypedCache[T]) LockForUpdate(ctx context.Context, key string, owner string) error {
return tc.cache.LockForUpdate(ctx, key, owner)
}
// UnlockForUpdate unlocks a cache entry that was previously locked for update, allowing other operations to modify the entry.
// The owner of the lock must be provided to successfully unlock the entry.
//
// Parameters:
// - ctx: The context to control cancellation and timeouts.
// - key: The unique identifier for the cache entry to be unlocked.
// - owner: The owner of the lock, which was used to lock the entry.
//
// Returns:
// - error: If the cache operation fails, an error is returned.
//
// Example:
// To unlock a cache entry after an update:
//
// err := UnlockForUpdate(ctx, "user_01J4YHWG45SC7VW684TZB2SZ7K", "service_01J4ZXVMKDFTSZGYKT5FXAZAB4")
// if err != nil {
// log.Fatalf("Failed to unlock cache key after update: %v", err)
// }
func (tc *TypedCache[T]) UnlockForUpdate(ctx context.Context, key string, owner string) error {
return tc.cache.UnlockForUpdate(ctx, key, owner)
}