Skip to main content

Cache

platform/cache separates cache semantics from its store.

Construct a cache

store, err := rediscache.Open(ctx, cfg)
if err != nil {
return fmt.Errorf("open Redis cache: %w", err)
}

c := cache.New(
store,
cache.WithPrefix("dx-catalogue-go"),
cache.WithDefaultTTL(5*time.Minute),
)
defer c.Close()

Use cache.NewMemory in tests or single-process scenarios where shared state is not required.

Scoped keys

items := c.Namespace("catalogue").Namespace("items").TTL(time.Minute)

var item Item
err := items.Get(ctx, itemID, &item)
switch {
case err == nil:
return item, nil
case errors.Is(err, cache.ErrMiss):
// Load from the source of truth.
default:
return Item{}, fmt.Errorf("read item cache: %w", err)
}

Namespace and TTL return immutable scopes. Get, Set, SetTTL, Delete, Exists, Invalidate, Lock, Allow, and Key operate within the scope. Root Invalidate is rejected to prevent an accidental whole-cache flush.

Read-through and write invalidation

item, err := cache.GetOrLoad(
ctx,
items,
itemID,
func(ctx context.Context) (Item, error) {
return repo.Get(ctx, itemID)
},
)

GetOrLoad collapses concurrent in-process loads. Invalidating runs a mutation and removes the key after success. GetOrLoadAhead supports refresh-ahead behavior.

Cache failure must not silently become authoritative state. Decide whether the operation can continue from its primary store, and emit a metric for degraded cache behavior.

Coordination primitives

Lock is non-blocking and returns ErrLockHeld under contention. Allow is a fixed-window rate counter. Do not use either as a transaction or durable workflow substitute.