cache

package
v1.69.13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 27, 2025 License: Apache-2.0 Imports: 7 Imported by: 0

README

Frame Cache Package

A powerful, generic cache implementation with automatic serialization and swappable backends for the Frame service framework.

Features

  • Generic Type-Safe API: Cache any Go type with automatic serialization
  • Multiple Backends: In-memory, Redis, and Valkey implementations
  • Lazy Loading: Redis and Valkey backends are in subpackages and only loaded when used
  • Automatic Serialization: JSON and Gob serializers included, custom serializers supported
  • Flexible Keys: Use any comparable type as cache keys
  • Thread-Safe: All operations are concurrent-safe
  • TTL Support: Automatic expiration of cached items
  • Prefix Support: Namespace isolation for multi-tenancy
  • Raw Cache API: Direct byte-level operations when needed

Quick Start

import "github.com/pitabwire/frame/cache"

// 1. Create manager
manager := cache.NewManager()
defer manager.Close()

// 2. Add in-memory cache
manager.AddCache("users", cache.NewInMemoryCache())

// 3. Get typed cache
type User struct {
    ID   string `json:"id"`
    Name string `json:"name"`
}
userCache, _ := cache.GetCache[string, User](manager, "users", nil, nil)

// 4. Use it
ctx := context.Background()
user := User{ID: "123", Name: "John"}
userCache.Set(ctx, user.ID, user, 1*time.Hour)

// 5. Retrieve
cachedUser, found, _ := userCache.Get(ctx, "123")

Architecture

cache/
├── cache.go           # Core interfaces and generic wrapper
├── inmemory.go        # In-memory implementation (no external deps)
├── redis/
│   └── redis.go       # Redis implementation (lazy loaded)
└── valkey/
    └── valkey.go      # Valkey implementation (lazy loaded)
Key Components
Cache[K, V] - Generic Cache Interface

Type-safe cache interface with automatic serialization:

type Cache[K comparable, V any] interface {
    Get(ctx context.Context, key K) (V, bool, error)
    Set(ctx context.Context, key K, value V, ttl time.Duration) error
    Delete(ctx context.Context, key K) error
    Exists(ctx context.Context, key K) (bool, error)
    Flush(ctx context.Context) error
    Close() error
}
RawCache - Low-Level Cache Interface

Direct byte-level operations:

type RawCache interface {
    Get(ctx context.Context, key string) ([]byte, bool, error)
    Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
    // ... additional methods including Increment/Decrement
}
Serializer - Pluggable Serialization
type Serializer interface {
    Marshal(v any) ([]byte, error)
    Unmarshal(data []byte, v any) error
}

Built-in serializers:

  • JSONSerializer - JSON encoding (default)
  • GobSerializer - Go's gob encoding

Usage

Basic Usage with Service
import (
    "github.com/pitabwire/frame"
    "github.com/pitabwire/frame/cache"
)

type User struct {
    ID    string `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

// Create service with cache
ctx, svc := frame.NewService("myapp",
    frame.WithInMemoryCache("users"),
)

// Get cache manager from service
manager := svc.CacheManager()

// Get typed cache - automatic JSON serialization
userCache, ok := cache.GetCache[string, User](manager, "users", nil, nil)

// Store user
user := User{ID: "123", Name: "John", Email: "[email protected]"}
userCache.Set(ctx, user.ID, user, 1*time.Hour)

// Retrieve user
cachedUser, found, _ := userCache.Get(ctx, "123")
if found {
    fmt.Printf("User: %s\n", cachedUser.Name)
}
Standalone Cache Manager
import "github.com/pitabwire/frame/cache"

// Create manager
manager := cache.NewManager()
defer manager.Close()

// Add cache
manager.AddCache("products", cache.NewInMemoryCache())

// Get typed cache with int keys
type Product struct {
    ID    int     `json:"id"`
    Name  string  `json:"name"`
    Price float64 `json:"price"`
}

productCache, _ := cache.GetCache[int, Product](manager, "products", nil, nil)

// Use it
product := Product{ID: 1, Name: "Widget", Price: 9.99}
productCache.Set(ctx, product.ID, product, 1*time.Hour)
Redis Cache (Lazy Loaded)
import (
    "github.com/pitabwire/frame/cache"
    cacheredis "github.com/pitabwire/frame/cache/redis"
)

// Create Redis cache
redisCache, err := cacheredis.New(cacheredis.Options{
    Addr:     "localhost:6379",
    Password: "",
    DB:       0,
})
if err != nil {
    log.Fatal(err)
}

manager := cache.NewManager()
manager.AddCache("redis", redisCache)

// Get typed cache
userCache, _ := cache.GetCache[string, User](manager, "redis", nil, nil)
Valkey Cache (Lazy Loaded)
import (
    "github.com/pitabwire/frame/cache"
    cachevalkey "github.com/pitabwire/frame/cache/valkey"
)

// Create Valkey cache
valkeyCache, err := cachevalkey.New(cachevalkey.Options{
    Addr:     "localhost:6379",
    Password: "",
    DB:       0,
})
if err != nil {
    log.Fatal(err)
}

manager := cache.NewManager()
manager.AddCache("valkey", valkeyCache)

// Get typed cache with custom key function
productCache, _ := cache.GetCache[int, Product](
    manager,
    "valkey",
    nil,
    func(id int) string { return fmt.Sprintf("product:%d", id) },
)
Custom Serializer
// Use Gob serializer instead of JSON
userCache, _ := cache.GetCache[string, User](
    manager,
    "users",
    &cache.GobSerializer{},
    nil,
)
Custom Key Function
// Custom key formatting
userCache, _ := cache.GetCache[string, User](
    manager,
    "users",
    nil,
    func(userID string) string {
        return fmt.Sprintf("user:%s", userID)
    },
)
Raw Cache Operations
// Get raw cache for byte-level operations
rawCache, _ := manager.GetRawCache("users")

// Direct byte operations
rawCache.Set(ctx, "binary:data", []byte{0x48, 0x65, 0x6c, 0x6c, 0x6f}, 0)

// Counter operations
count, _ := rawCache.Increment(ctx, "page:views", 1)
fmt.Printf("Page views: %d\n", count)

Dependency Management

The cache package is designed to minimize dependencies:

  • Core Cache Package (cache/) - No external dependencies except standard library
  • In-Memory Cache - No external dependencies
  • Redis Cache (cache/redis/) - Only loaded when imported, requires github.com/redis/go-redis/v9
  • Valkey Cache (cache/valkey/) - Only loaded when imported, requires github.com/valkey-io/valkey-go
Example go.mod

If you only use in-memory cache:

require (
    github.com/pitabwire/frame v1.0.0
    // No redis dependency needed!
)

If you use Redis:

require (
    github.com/pitabwire/frame v1.0.0
    github.com/redis/go-redis/v9 v9.14.1  // Only included if you import cache/redis
)

If you use Valkey:

require (
    github.com/pitabwire/frame v1.0.0
    github.com/valkey-io/valkey-go v1.0.67  // Only included if you import cache/valkey
)

Type Safety Examples

String Keys, Custom Values
type Session struct {
    UserID    string    `json:"user_id"`
    ExpiresAt time.Time `json:"expires_at"`
}

sessionCache, _ := cache.GetCache[string, Session](manager, "sessions", nil, nil)
Int Keys, Struct Values
type Product struct {
    ID          int     `json:"id"`
    Name        string  `json:"name"`
    Price       float64 `json:"price"`
    InStock     bool    `json:"in_stock"`
}

productCache, _ := cache.GetCache[int, Product](manager, "products", nil, nil)
UUID Keys
import "github.com/google/uuid"

type Order struct {
    ID     uuid.UUID `json:"id"`
    Total  float64   `json:"total"`
    Status string    `json:"status"`
}

orderCache, _ := cache.GetCache[uuid.UUID, Order](
    manager,
    "orders",
    nil,
    func(id uuid.UUID) string { return id.String() },
)
Nested Structures
type Address struct {
    Street  string `json:"street"`
    City    string `json:"city"`
    Country string `json:"country"`
}

type Customer struct {
    ID      string  `json:"id"`
    Name    string  `json:"name"`
    Address Address `json:"address"`
}

customerCache, _ := cache.GetCache[string, Customer](manager, "customers", nil, nil)

Performance Considerations

In-Memory Cache
  • Pros: Zero latency, no network overhead, no external dependencies
  • Cons: Limited by RAM, not distributed, lost on restart
  • Best for: Small datasets, development, single-instance apps, hot data
Redis Cache
  • Pros: Persistent, distributed, scales horizontally, battle-tested
  • Cons: Network latency, requires Redis server, external dependency
  • Best for: Large datasets, production, multi-instance apps, shared cache
Valkey Cache
  • Pros: Redis-compatible, open-source, uses official valkey-go client, community-driven
  • Cons: Network latency, requires Valkey server, external dependency
  • Best for: Same as Redis, when you prefer open-source alternatives with native client support
Optimization Tips
  1. Choose appropriate serializers: JSON for interoperability, Gob for Go-only performance
  2. Set reasonable TTLs to prevent unbounded growth
  3. Use raw cache for simple byte operations (skip serialization overhead)
  4. Customize key functions to optimize key formatting
  5. Use appropriate cache backend: In-memory for hot data, Redis/Valkey for shared/persistent cache

Testing

The cache package includes comprehensive tests with real container integration:

# Run all cache tests (includes InMemory, Redis, and Valkey with real containers)
go test -v ./cache/

# Test specific implementations
go test -v ./cache/redis/    # Requires Docker
go test -v ./cache/valkey/   # Requires Docker

All implementations run through the same unified test suite, ensuring consistent behavior across backends.

Best Practices

  1. Use generics for type safety: Leverage Cache[K, V] instead of raw bytes
  2. Choose appropriate serializers: JSON for external systems, Gob for internal
  3. Lazy load backends: Only import redis/valkey when needed
  4. Use raw cache sparingly: Only for counters and binary data
  5. Set TTLs appropriately: Balance freshness vs. load
  6. Handle cache misses gracefully: Always check the found boolean
  7. Close caches properly: Always defer cache.Close() or manager.Close()
  8. Use appropriate cache backends: In-memory for development, Redis/Valkey for production

License

Part of the Frame service framework.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Cache

type Cache[K comparable, V any] interface {
	// Get retrieves an item from the cache
	Get(ctx context.Context, key K) (V, bool, error)

	// Set sets an item in the cache with the specified TTL
	Set(ctx context.Context, key K, value V, ttl time.Duration) error

	// Delete removes an item from the cache
	Delete(ctx context.Context, key K) error

	// Exists checks if a key exists in the cache
	Exists(ctx context.Context, key K) (bool, error)

	// Flush clears all items from the cache
	Flush(ctx context.Context) error

	// Close releases any resources used by the cache
	Close() error
}

Cache is a generic cache interface with automatic serialization.

func GetCache

func GetCache[K comparable, V any](
	manager Manager,
	name string,
	keyFunc func(K) string,
) (Cache[K, V], bool)

GetCache returns a typed cache with automatic serialization.

func NewGenericCache

func NewGenericCache[K comparable, V any](raw RawCache, keyFunc func(K) string) Cache[K, V]

NewGenericCache creates a new generic cache with automatic serialization.

type GenericCache

type GenericCache[K comparable, V any] struct {
	// contains filtered or unexported fields
}

GenericCache wraps a RawCache and provides automatic serialization.

func (*GenericCache[K, V]) Close

func (g *GenericCache[K, V]) Close() error

func (*GenericCache[K, V]) Delete

func (g *GenericCache[K, V]) Delete(ctx context.Context, key K) error

func (*GenericCache[K, V]) Exists

func (g *GenericCache[K, V]) Exists(ctx context.Context, key K) (bool, error)

func (*GenericCache[K, V]) Flush

func (g *GenericCache[K, V]) Flush(ctx context.Context) error

func (*GenericCache[K, V]) Get

func (g *GenericCache[K, V]) Get(ctx context.Context, key K) (V, bool, error)

func (*GenericCache[K, V]) Set

func (g *GenericCache[K, V]) Set(ctx context.Context, key K, value V, ttl time.Duration) error

type InMemoryCache

type InMemoryCache struct {
	// contains filtered or unexported fields
}

InMemoryCache is a thread-safe in-memory cache implementation.

func (*InMemoryCache) Close

func (c *InMemoryCache) Close() error

Close stops the cleanup goroutine and releases resources.

func (*InMemoryCache) Decrement

func (c *InMemoryCache) Decrement(ctx context.Context, key string, delta int64) (int64, error)

Decrement atomically decrements a counter.

func (*InMemoryCache) Delete

func (c *InMemoryCache) Delete(_ context.Context, key string) error

Delete removes an item from the cache.

func (*InMemoryCache) Exists

func (c *InMemoryCache) Exists(_ context.Context, key string) (bool, error)

Exists checks if a key exists in the cache.

func (*InMemoryCache) Flush

func (c *InMemoryCache) Flush(_ context.Context) error

Flush clears all items from the cache.

func (*InMemoryCache) Get

func (c *InMemoryCache) Get(_ context.Context, key string) ([]byte, bool, error)

Get retrieves an item from the cache.

func (*InMemoryCache) Increment

func (c *InMemoryCache) Increment(_ context.Context, key string, delta int64) (int64, error)

Increment atomically increments a counter.

func (*InMemoryCache) Set

func (c *InMemoryCache) Set(_ context.Context, key string, value []byte, ttl time.Duration) error

Set sets an item in the cache with the specified TTL.

type Manager

type Manager interface {
	AddCache(name string, cache RawCache)
	GetRawCache(name string) (RawCache, bool)
	RemoveCache(name string) error
	Close() error
}

func NewManager

func NewManager() Manager

NewManager creates a new manager.

type Option added in v1.63.3

type Option func(*Options)

Option configures database connection settings.

func WithCredsFile added in v1.67.16

func WithCredsFile(credsFile string) Option

func WithDSN added in v1.63.3

func WithDSN(dsn data.DSN) Option

func WithMaxAge added in v1.63.3

func WithMaxAge(maxAge time.Duration) Option

WithMaxAge returns an Option to configure the max age of the cache.

func WithName added in v1.64.11

func WithName(name string) Option

type Options added in v1.63.3

type Options struct {
	DSN       data.DSN
	CredsFile string
	Name      string
	MaxAge    time.Duration
}

Options holds Datastore connection configuration.

type RawCache

type RawCache interface {
	Get(ctx context.Context, key string) ([]byte, bool, error)
	Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
	Delete(ctx context.Context, key string) error
	Exists(ctx context.Context, key string) (bool, error)
	Flush(ctx context.Context) error
	Close() error
	Increment(ctx context.Context, key string, delta int64) (int64, error)
	Decrement(ctx context.Context, key string, delta int64) (int64, error)
}

RawCache is the low-level cache interface that works with bytes.

func NewInMemoryCache

func NewInMemoryCache() RawCache

NewInMemoryCache creates a new in-memory cache.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL