Skip to content

Cache

λ Cosmos provides a caching abstraction through the contract.Cache interface with two built-in implementations: in-memory and Redis.

type Cache interface {
Get(ctx context.Context, key string) (any, error)
Put(ctx context.Context, key string, value any, ttl time.Duration) error
Delete(ctx context.Context, key string) error
Has(ctx context.Context, key string) (bool, error)
Pull(ctx context.Context, key string) (any, error)
Forever(ctx context.Context, key string, value any) error
Increment(ctx context.Context, key string, by int64) (int64, error)
Decrement(ctx context.Context, key string, by int64) (int64, error)
Remember(ctx context.Context, key string, ttl time.Duration, compute func() (any, error)) (any, error)
RememberForever(ctx context.Context, key string, compute func() (any, error)) (any, error)
}

When a key is not found, Get and Pull return contract.ErrCacheKeyNotFound:

val, err := cache.Get(ctx, "user:42")
if errors.Is(err, contract.ErrCacheKeyNotFound) {
// key does not exist or has expired
}

An in-memory cache suitable for development, testing, and single-instance applications:

import "github.com/studiolambda/cosmos/framework/cache"
c := cache.NewMemory(5*time.Minute, 10*time.Minute)

The two parameters are:

  • Default expiration — TTL applied when using Put with a zero duration.
  • Cleanup interval — How often expired items are removed from memory.

A Redis-backed cache for distributed applications:

import "github.com/studiolambda/cosmos/framework/cache"
c := cache.NewRedis(&cache.RedisOptions{
Addr: "localhost:6379",
Password: "",
DB: 0,
})

Or wrap an existing *redis.Client:

c := cache.NewRedisFrom(existingRedisClient)
// Store a value for 10 minutes
err := cache.Put(ctx, "user:42", user, 10*time.Minute)
// Retrieve it
val, err := cache.Get(ctx, "user:42")

Store a value that never expires:

err := cache.Forever(ctx, "config:site_name", "My App")

Check existence without retrieving:

exists, err := cache.Has(ctx, "user:42")

Remove a cached value:

err := cache.Delete(ctx, "user:42")

Retrieve and remove in one atomic operation:

val, err := cache.Pull(ctx, "one-time-token:abc123")
// val is returned, key is deleted

Atomically modify integer values:

newVal, err := cache.Increment(ctx, "page_views", 1)
newVal, err := cache.Decrement(ctx, "remaining_quota", 1)

The key must already exist or contract.ErrCacheKeyNotFound is returned (for the memory implementation). The Redis implementation creates the key if it doesn’t exist.

Get a cached value or compute and store it if missing:

val, err := cache.Remember(ctx, "user:42", 10*time.Minute, func() (any, error) {
return db.FindUser(ctx, 42)
})

This is the most common caching pattern — it avoids the check-then-set race condition by combining the operations:

// Without Remember (race condition possible):
val, err := cache.Get(ctx, key)
if errors.Is(err, contract.ErrCacheKeyNotFound) {
val, err = expensiveComputation()
cache.Put(ctx, key, val, ttl)
}
// With Remember (atomic):
val, err := cache.Remember(ctx, key, ttl, expensiveComputation)

RememberForever works the same but stores the value with no expiration.