Documentation
¶
Overview ¶
Package cache provides a generic caching interface with support for multiple backend implementations including Redis and in-memory stores.
Index ¶
Examples ¶
Constants ¶
const NoExpiry time.Duration = -1
NoExpiry is the WithExpiry value for entries that should never expire. It is distinct from zero: a zero expiry means "use the cache's configured default".
Variables ¶
var ( ErrNotFound = errors.New("not found") // ErrNamespaceRequired indicates a Flush (or an unscoped DeleteByPrefix) // was attempted on a provider whose backing store may be shared and which // has no configured key namespace — so it cannot know which entries it // owns. Configure a namespace (e.g. the redis provider's // Config.Namespace) to enable whole-cache operations. ErrNamespaceRequired = errors.New("operation requires a configured key namespace") // its backing store is unreachable — typically a tripped circuit breaker. // // It is deliberately distinct from ErrNotFound. A read that answers "not // found" during an outage is indistinguishable from one that answers it // because the key really is absent, and a caller whose whole purpose is to // notice absence — idempotency's FailClosed, say — is then told exactly what // it must not be told. A write that answers nil during an outage is worse: // it reports that a delete happened, and the stale value it did not delete // is served for the rest of its TTL. // // Callers that would rather treat unavailability as a miss can say so, by // checking for this error; callers that must not cannot be tricked into it. ErrUnavailable = errors.New("cache is unavailable") )
Functions ¶
func EffectiveExpiry ¶
EffectiveExpiry resolves a write's options against a cache's default expiry, returning the duration the entry should live: a positive duration, or zero meaning "never expire". The three input states resolve as documented on WithExpiry — an unset/zero expiry takes defaultExpiry, and a negative expiry (NoExpiry) or negative default resolves to no expiry. Providers should treat this as the single source of truth for expiry semantics so backends cannot drift.
Types ¶
type Cache ¶
type Cache[T any] interface { Get(ctx context.Context, key string) (*T, error) // GetMany fetches multiple keys in as few round trips as possible. // Missing keys are omitted from the returned map, so a key's absence // from the result is a cache miss. GetMany(ctx context.Context, keys []string) (map[string]*T, error) Set(ctx context.Context, key string, value *T, opts ...WriteOption) error // SetMany stores multiple values at once. WriteOptions apply to the // whole batch: every item gets the same expiry resolution as a single // Set call would. SetMany(ctx context.Context, items map[string]*T, opts ...WriteOption) error Delete(ctx context.Context, key string) error // DeleteMany removes multiple keys in as few round trips as possible. // Keys that are already absent are not an error. DeleteMany(ctx context.Context, keys []string) error // DeleteByPrefix removes every entry whose key begins with prefix. // Providers on shared backing stores that have no configured // namespace reject an empty prefix with ErrNamespaceRequired, since // that would delete entries they cannot prove they own. DeleteByPrefix(ctx context.Context, prefix string) error // Flush removes every entry this cache owns. Providers whose backing // store may be shared (redis) require a configured namespace to know // what they own and return ErrNamespaceRequired without one; // providers that wholly own their store (memory) always succeed. Flush(ctx context.Context) error Ping(ctx context.Context) error // Close releases the resources the cache holds — a connection pool, a // background sweep — and is safe to call more than once. It does not // evict anything: entries in a shared backing store outlive the handle // that wrote them. After Close the cache must not be used again. Close() error }
Cache is our wrapper interface for a cache. Batched reads, writes, and deletes are part of the interface — every provider implements them, and batching is the primary access pattern for high-volume consumers.
Writes accept optional WriteOptions. A write with no options (or a zero expiry) uses the cache's configured default expiry; WithExpiry overrides it per call, and WithExpiry(NoExpiry) pins the entry against expiry entirely.
Keys are scoped to the cache instance: a provider with a configured namespace prepends it to every key transparently, so callers never see or supply namespaced keys. There is deliberately no way to reach entries outside the cache's own namespace — a generic inspect-everything client is a debugging tool, not a production surface, and belongs in a separate Debug type if it is ever needed.
Example (Batch) ¶
package main
import (
"context"
"fmt"
"github.com/primandproper/platform-go/v9/cache/memory"
)
func main() {
ctx := context.Background()
c, err := memory.NewInMemoryCache[string](0)
if err != nil {
panic(err)
}
// Batched reads and writes are part of Cache itself — no assertion needed.
one, two := "one", "two"
if err = c.SetMany(ctx, map[string]*string{"k1": &one, "k2": &two}); err != nil {
panic(err)
}
// Missing keys are simply absent from the result.
results, err := c.GetMany(ctx, []string{"k1", "k2", "missing"})
if err != nil {
panic(err)
}
fmt.Println(len(results))
fmt.Println(*results["k1"])
}
Output: 2 one
Example (NotFound) ¶
package main
import (
"context"
"errors"
"fmt"
"github.com/primandproper/platform-go/v9/cache"
"github.com/primandproper/platform-go/v9/cache/memory"
)
func main() {
ctx := context.Background()
c, cacheErr := memory.NewInMemoryCache[string](0)
if cacheErr != nil {
panic(cacheErr)
}
_, err := c.Get(ctx, "nonexistent")
fmt.Println(err)
fmt.Println(errors.Is(err, cache.ErrNotFound))
}
Output: not found true
Example (SetAndGet) ¶
package main
import (
"context"
"fmt"
"github.com/primandproper/platform-go/v9/cache/memory"
)
func main() {
ctx := context.Background()
c, err := memory.NewInMemoryCache[string](0)
if err != nil {
panic(err)
}
value := "cached-value"
if err = c.Set(ctx, "my-key", &value); err != nil {
panic(err)
}
result, err := c.Get(ctx, "my-key")
if err != nil {
panic(err)
}
fmt.Println(*result)
}
Output: cached-value
type Codec ¶
Codec converts values to and from the byte representation a serializing provider stores (redis today; the memory provider holds values directly and never encodes). NewGobCodec is the default; consumers with tight size or latency budgets — large batch reads where per-value codec overhead is the tail latency — supply their own fixed-format Codec through the provider's constructor options.
A Codec must be safe for concurrent use, and Decode must round-trip whatever Encode produced. Values written with one codec are unreadable through another, so changing a deployed cache's codec requires either a key change or a flush.
func NewGobCodec ¶
NewGobCodec returns the default gob-backed Codec. Types must be gob-friendly: exported fields only, and interface-typed fields need their concrete types registered with gob.Register.
Example ¶
ExampleNewGobCodec shows the codec the serializing providers use by default. Consumers reach for Codec only to replace it — redis.WithCodec accepts any implementation whose Decode round-trips its own Encode. Note the migration caveat: values written under one codec are unreadable through another.
package main
import (
"fmt"
"github.com/primandproper/platform-go/v9/cache"
)
func main() {
type session struct {
UserID string
Roles []string
}
codec := cache.NewGobCodec[session]()
encoded, err := codec.Encode(&session{UserID: "u-1", Roles: []string{"admin", "auditor"}})
if err != nil {
panic(err)
}
decoded, err := codec.Decode(encoded)
if err != nil {
panic(err)
}
fmt.Println(decoded.UserID, decoded.Roles)
}
Output: u-1 [admin auditor]
type WriteConfig ¶
type WriteConfig struct {
// Expiry holds the caller's requested expiry: zero for "use the
// cache's default", NoExpiry (or any negative value) for "never
// expire", or a positive duration.
Expiry time.Duration
}
WriteConfig is the resolved per-call write configuration. Providers normally consume it through EffectiveExpiry rather than directly; it is exported so third-party Cache implementations can run the same resolution.
type WriteOption ¶
type WriteOption func(*WriteConfig)
WriteOption configures a single Set or SetMany call.
func WithExpiry ¶
WithExpiry sets the expiry for the entries written by this call. Zero defers to the cache's configured default; NoExpiry (or any negative duration) stores the entries without expiry.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package cachemock provides moq-generated mock implementations of interfaces in the cache package.
|
Package cachemock provides moq-generated mock implementations of interfaces in the cache package. |
|
slots
Package slots produces Redis Cluster keys whose slots are pre-planned to distribute evenly across the cluster's nodes.
|
Package slots produces Redis Cluster keys whose slots are pre-planned to distribute evenly across the cluster's nodes. |