dgcache

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Dec 27, 2025 License: MIT Imports: 12 Imported by: 1

README

dg-cache

Abstract caching layer for the dg-framework. Provides a unified API for various cache drivers with support for serialization, tagging, atomic operations, and the "Remember" pattern.

Installation

go get github.com/donnigundala/dg-cache@v1.0.0

Features

  • 🚀 Unified API - Simple, consistent interface across all drivers
  • 🔄 Multiple Stores - Use different drivers for different purposes
  • 💾 Built-in Drivers - Memory (testing) and Redis (production) included
  • 🔧 Extensible - Easy to add custom drivers
  • 📦 Serialization - Automatic marshaling/unmarshaling with JSON or Msgpack
  • 🗜️ Compression - Transparent Gzip compression for large values
  • 📊 Observability - Standardized metrics and Prometheus exporter
  • 🛡️ Reliability - Circuit breaker and enhanced retry logic
  • 🏷️ Tagged Cache - Group related items with tags (Redis and Memory drivers)
  • Performance - LRU eviction, metrics, and optimized serialization

Package Structure

dg-cache/
├── config.go              # Configuration structures and validation
├── manager.go             # Cache manager (multi-store orchestration)
├── store.go               # Store, TaggedStore, and Driver interfaces
├── helpers.go             # Typed retrieval helpers (GetString, GetInt, etc.)
├── errors.go              # Custom error types
├── drivers/
│   ├── memory/           # In-memory cache driver
│   │   ├── memory.go     # Core driver implementation
│   │   ├── lru.go        # LRU eviction policy
│   │   ├── metrics.go    # Metrics collection
│   │   └── config.go     # Memory driver configuration
│   └── redis/            # Redis cache driver
│       ├── redis.go      # Core driver implementation
│       ├── tagged.go     # Tagged cache support
│       └── config.go     # Redis driver configuration
├── serializer/
│   ├── serializer.go     # Serializer interface
│   ├── json.go           # JSON serializer
│   └── msgpack.go        # Msgpack serializer
└── docs/
    ├── API.md            # Complete API reference
    ├── SERIALIZATION.md  # Serialization guide
    ├── MEMORY_DRIVER.md  # Memory driver documentation
    └── REDIS_DRIVER.md   # Redis driver documentation

Core Concepts

Manager

The Manager is the central orchestrator that manages multiple cache stores, provides a unified interface across all drivers, handles driver registration, and routes cache operations to the appropriate store.

Store Interface

Defines the contract that all cache drivers must implement:

  • Basic operations: Get, Put, Forget, Flush
  • Batch operations: GetMultiple, PutMultiple
  • Atomic operations: Increment, Decrement
  • TTL support: Forever (no expiration)
  • Existence checks: Has, Missing
Driver

Extends the Store interface with driver-specific functionality like Name() for identification and Close() for resource cleanup.

TaggedStore

Optional interface for drivers that support cache tagging to group related cache items and flush them together. Supported by both Redis and Memory drivers.

Included Drivers

Memory Driver (drivers/memory)
  • In-memory caching for development/testing
  • LRU eviction with configurable size limits
  • Tagged cache support (v1.6.1)
  • Metrics tracking (hits, misses, evictions)
  • Thread-safe operations
Redis Driver (drivers/redis)
  • Production-ready Redis caching
  • JSON and Msgpack serialization
  • Tagged cache support
  • Shared client support
  • Connection pooling

Quick Start

package main

import (
    "context"
    "log"
    "time"
    "github.com/donnigundala/dg-core/foundation"
    "github.com/donnigundala/dg-cache"
)

func main() {
    app := foundation.New(".")
    
    // Register provider (uses 'cache' key in config)
    app.Register(dgcache.NewCacheServiceProvider(nil))
    
    if err := app.Boot(); err != nil {
        log.Fatal(err)
    }
    
    // Usage
    cacheMgr := dgcache.MustResolve(app)
    ctx := context.Background()
    
    cacheMgr.Put(ctx, "key", "value", 10*time.Minute)
    val, _ := cacheMgr.Get(ctx, "key")
}
Integration via InfrastructureSuite

In your bootstrap/app.go, you typically use the declarative suite pattern:

func InfrastructureSuite(workerMode bool) []foundation.ServiceProvider {
	return []foundation.ServiceProvider{
		dgcache.NewCacheServiceProvider(nil),
		// ... other providers
	}
}
Caching Complex Types
type User struct {
    ID    int
    Name  string
    Email string
}

// Store any Go type - automatic serialization!
user := User{ID: 1, Name: "John", Email: "john@example.com"}
manager.Put(ctx, "user:1", user, 1*time.Hour)

// Retrieve with type assertion
val, _ := manager.Get(ctx, "user:1")
user = val.(User)

// Or use type-safe helper
var user User
manager.GetAs(ctx, "user:1", &user)
Typed Helpers
// Type-safe retrieval methods
name, err := manager.GetString(ctx, "name")
age, err := manager.GetInt(ctx, "age")
score, err := manager.GetFloat64(ctx, "score")
active, err := manager.GetBool(ctx, "active")

// Generic type-safe method
var config map[string]interface{}
err := manager.GetAs(ctx, "config", &config)
Memory Driver with Limits
manager, _ := cache.NewManager(cache.Config{
    DefaultStore: "memory",
    Stores: map[string]cache.StoreConfig{
        "memory": {
            Driver: "memory",
            Options: map[string]interface{}{
                "max_items":        1000,              // Max 1000 items
                "max_bytes":        10 * 1024 * 1024,  // Max 10MB
                "eviction_policy":  "lru",             // LRU eviction
                "cleanup_interval": 1 * time.Minute,   // Cleanup every minute
                "enable_metrics":   true,              // Enable statistics
            },
        },
    },
})
Redis with Msgpack Serialization
import (
    "github.com/donnigundala/dg-cache"
    "github.com/donnigundala/dg-cache/drivers/redis"
)

// Option 1: Create driver with config
redisDriver, _ := redis.NewDriver(cache.StoreConfig{
    Options: map[string]interface{}{
        "host":       "localhost",
        "port":       6379,
        "serializer": "msgpack", // or "json"
    },
})

// Option 2: Use shared Redis client
client := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
redisDriver := redis.NewDriverWithClient(client, "app")
Remember Pattern

Retrieve an item from the cache, or execute the callback and store the result if it doesn't exist.

user, err := manager.Remember(ctx, "user:1", 1*time.Hour, func() (interface{}, error) {
    return db.FindUser(1)
})
Atomic Operations
// Increment
newVal, err := manager.Increment(ctx, "hits", 1)

// Decrement
newVal, err := manager.Decrement(ctx, "hits", 1)
Multiple Stores
// Access specific store
redisStore, err := manager.Store("redis")
redisStore.Put(ctx, "key", "value", 0)

// Access default store
manager.Put(ctx, "key", "value", 0)

Container Integration (v1.6.0)

As of v1.6.0, dg-cache is fully integrated with the dg-core container system. Named cache stores are automatically registered in the container as cache.<name>.

Access Patterns
1. Direct Resolution

You can resolve specific stores directly from the container:

// Resolve named stores
redisStore, _ := app.Make("cache.redis")
memStore, _ := app.Make("cache.memory")

// Resolve main cache manager
cacheManager, _ := app.Make("cache")
2. Helper Functions

Global helper functions provide a more convenient and type-safe way to resolve the cache:

import "github.com/donnigundala/dg-cache"

// Resolve main cache
mgr := cache.MustResolve(app)

// Resolve named store
redis := cache.MustResolveStore(app, "redis")

The Injectable struct simplifies dependency injection in your services:

import (
    "github.com/donnigundala/dg-core/foundation"
    "github.com/donnigundala/dg-cache"
)

type UserService struct {
    inject *cache.Injectable
}

func NewUserService(app foundation.Application) *UserService {
    return &UserService{
        inject: cache.NewInjectable(app),
    }
}

func (s *UserService) CacheUser(ctx context.Context, user *User) {
    // Use default cache store
    s.inject.Cache().Put(ctx, "user:1", user, 0)

    // Use specific store (e.g. redis)
    s.inject.Store("redis").Put(ctx, "user:1", user, 0)
}

Configuration

The plugin uses the cache key in your configuration file.

Configuration Mapping (YAML vs ENV)
YAML Key Environment Variable Default Description
cache.default_store CACHE_DRIVER memory Default store name
cache.prefix CACHE_PREFIX dg_cache Global key prefix
cache.stores.<name>.driver - - redis, memory
cache.stores.<name>.prefix - - Store-specific prefix
cache.stores.<name>.connection - default Redis connection name
Example YAML
cache:
  default_store: redis
  prefix: app_
  stores:
    memory:
      driver: memory
    redis:
      driver: redis
      connection: default
Compression

Enable transparent Gzip compression to save storage space for large values (Redis driver only):

Options: map[string]interface{}{
    "compression": "gzip",
}

Memory Driver Features

Size Limits
Options: map[string]interface{}{
    "max_items": 1000,              // Maximum number of items
    "max_bytes": 10 * 1024 * 1024,  // Maximum total size (10MB)
}
LRU Eviction

Automatically evicts least recently used items when limits are reached:

Options: map[string]interface{}{
    "eviction_policy": "lru",  // Least Recently Used
}
Metrics
Options: map[string]interface{}{
    "enable_metrics": true,
}

// Get statistics
driver := manager.Store("memory").(*memory.Driver)
stats := driver.Stats()
fmt.Printf("Hit rate: %.2f%%\n", stats.HitRate*100)
fmt.Printf("Items: %d, Bytes: %d\n", stats.ItemCount, stats.BytesUsed)

📊 Observability

dg-cache is instrumented with OpenTelemetry metrics. As of v2.0.0, the legacy Prometheus collector has been replaced with native OpenTelemetry instruments.

Standardized Metrics

The following metrics are automatically collected from all active cache stores via asynchronous observers:

  • cache_hits_total: Counter (labels: cache_store)
  • cache_misses_total: Counter (labels: cache_store)
  • cache_sets_total: Counter (labels: cache_store)
  • cache_deletes_total: Counter (labels: cache_store)
  • cache_evictions_total: Counter (labels: cache_store)
  • cache_items: Gauge (labels: cache_store)
  • cache_bytes: Gauge (labels: cache_store)
Configuration

To enable observability, ensure the dg-observability plugin is registered and configured:

observability:
  enabled: true
  service_name: "my-app"

The metrics are automatically registered on application boot. No manual collector registration is required.

Reliability Features

Enhanced Retries (Redis)

Configure exponential backoff for Redis connections:

Options: map[string]interface{}{
    "max_retries":       3,
    "min_retry_backoff": 8 * time.Millisecond,
    "max_retry_backoff": 512 * time.Millisecond,
}
Circuit Breaker

Protect your application from cascading cache failures. If the cache becomes unresponsive, the circuit breaker opens and fails fast.

Options: map[string]interface{}{
    "circuit_breaker": map[string]interface{}{
        "enabled":   true,
        "threshold": 5,                 // Fail after 5 errors
        "timeout":   1 * time.Minute,   // Reset after 1 minute
    },
}

Creating Custom Drivers

Creating Custom Drivers

Implement the cache.Driver interface:

type Driver interface {
    Get(ctx context.Context, key string) (interface{}, error)
    Put(ctx context.Context, key string, value interface{}, ttl time.Duration) error
    Forget(ctx context.Context, key string) error
    Flush(ctx context.Context) error
    // ... other methods
}

Performance

Benchmarks
BenchmarkJSON_Marshal        7,542,747    152.6 ns/op    128 B/op    2 allocs/op
BenchmarkMsgpack_Marshal     5,384,852    210.9 ns/op    272 B/op    4 allocs/op
BenchmarkJSON_Unmarshal      2,329,303    443.5 ns/op    216 B/op    4 allocs/op
BenchmarkMsgpack_Unmarshal   6,601,837    172.1 ns/op     96 B/op    2 allocs/op

Msgpack is 2.6x faster for unmarshal operations!

Documentation

For detailed information, see the comprehensive documentation in the docs/ directory:

Version Information
  • Current Version: v1.3.0
  • Go Version: 1.21+
  • Test Coverage: 88%+
  • Status: Production Ready

Note: The dg-redis package has been merged into this package as drivers/redis in v1.3.0. If you're using the old dg-redis package, please migrate to github.com/donnigundala/dg-cache/drivers/redis.

License

MIT License - see LICENSE file for details.

Documentation

Index

Constants

View Source
const (
	Binding = "cache"
	Version = "1.0.0"
)

Variables

View Source
var (
	// ErrKeyNotFound is returned when a cache key is not found.
	ErrKeyNotFound = fmt.Errorf("cache: key not found")

	// ErrInvalidValue is returned when a cache value is invalid.
	ErrInvalidValue = fmt.Errorf("cache: invalid value")

	// ErrDriverNotFound is returned when a cache driver is not found.
	ErrDriverNotFound = fmt.Errorf("cache: driver not found")

	// ErrStoreNotFound is returned when a cache store is not found.
	ErrStoreNotFound = fmt.Errorf("cache: store not found")
)

Error types for cache operations.

Functions

func ErrDriverError

func ErrDriverError(driver string, err error) error

ErrDriverError returns a driver error with a formatted message.

func ErrInvalidConfig

func ErrInvalidConfig(format string, args ...interface{}) error

ErrInvalidConfig returns a configuration error with a formatted message.

func MustResolve

func MustResolve(app foundation.Application) cache.Cache

MustResolve resolves the cache manager or panics.

func MustResolveStore

func MustResolveStore(app foundation.Application, name string) cache.Store

MustResolveStore resolves a named store or panics.

func RegisterDriver

func RegisterDriver(name string, factory DriverFactory)

RegisterDriver registers a driver factory globally.

func Resolve

func Resolve(app foundation.Application) (cache.Cache, error)

Resolve resolves the main cache manager from the application container.

func ResolveStore

func ResolveStore(app foundation.Application, name string) (cache.Store, error)

ResolveStore resolves a named cache store from the container.

Types

type CacheServiceProvider

type CacheServiceProvider struct {
	// Config holds cache configuration
	Config Config `config:"cache"`

	// DriverFactories maps driver names to their factory functions
	DriverFactories map[string]DriverFactory
}

CacheServiceProvider implements the PluginProvider interface.

func NewCacheServiceProvider

func NewCacheServiceProvider(driverFactories map[string]DriverFactory) *CacheServiceProvider

NewCacheServiceProvider creates a new cache service provider.

func (*CacheServiceProvider) Boot

Boot boots the cache service provider.

func (*CacheServiceProvider) Dependencies

func (p *CacheServiceProvider) Dependencies() []string

Dependencies returns the list of dependencies.

func (*CacheServiceProvider) Name

func (p *CacheServiceProvider) Name() string

Name returns the name of the plugin.

func (*CacheServiceProvider) Register

Register registers the cache service provider.

func (*CacheServiceProvider) Shutdown

Shutdown gracefully closes cache connections.

func (*CacheServiceProvider) Version

func (p *CacheServiceProvider) Version() string

Version returns the version of the plugin.

type Config

type Config struct {
	// DefaultStore is the name of the default cache store to use.
	DefaultStore string `mapstructure:"default_store"`

	// Prefix is the global cache key prefix.
	// This is prepended to all cache keys.
	Prefix string `mapstructure:"prefix"`

	// Stores contains the configuration for each cache store.
	Stores map[string]StoreConfig `mapstructure:"stores"`
}

Config represents the cache configuration.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns a default cache configuration.

func (Config) Validate

func (c Config) Validate() error

Validate validates the cache configuration.

func (Config) WithDefaultStore

func (c Config) WithDefaultStore(name string) Config

WithDefaultStore sets the default store name.

func (Config) WithPrefix

func (c Config) WithPrefix(prefix string) Config

WithPrefix sets the global cache key prefix.

func (Config) WithStore

func (c Config) WithStore(name string, config StoreConfig) Config

WithStore adds a store configuration.

type DriverFactory

type DriverFactory func(config StoreConfig) (cache.Driver, error)

DriverFactory is a function that creates a cache driver.

type Injectable

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

Injectable provides a convenient way to inject cache dependencies.

func NewInjectable

func NewInjectable(app foundation.Application) *Injectable

NewInjectable creates a new Injectable instance.

func (*Injectable) Cache

func (i *Injectable) Cache() cache.Cache

Cache returns the main cache manager.

func (*Injectable) Store

func (i *Injectable) Store(name string) cache.Store

Store returns a named cache store.

func (*Injectable) TryStore

func (i *Injectable) TryStore(name string) cache.Store

TryStore returns a named store or nil if it doesn't exist.

type Item

type Item struct {
	// Key is the cache key.
	Key string

	// Value is the cached value.
	Value interface{}

	// ExpiresAt is the expiration time.
	// Zero value means the item never expires.
	ExpiresAt time.Time

	// Tags are the tags associated with this item.
	Tags []string
}

Item represents a cache item with metadata.

func (Item) IsExpired

func (i Item) IsExpired() bool

IsExpired checks if the item has expired.

func (Item) TTL

func (i Item) TTL() time.Duration

TTL returns the time until expiration. Returns 0 if the item has expired or never expires.

type Manager

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

Manager manages multiple cache stores and provides a unified interface.

func NewManager

func NewManager(config Config) (*Manager, error)

func (*Manager) Close

func (m *Manager) Close() error

Close closes all cache stores and releases resources.

func (*Manager) Decrement

func (m *Manager) Decrement(ctx context.Context, key string, value int64) (int64, error)

Decrement decrements a value in the default cache store.

func (*Manager) DefaultStore

func (m *Manager) DefaultStore() cache.Store

DefaultStore returns the default cache store.

func (*Manager) Flush

func (m *Manager) Flush(ctx context.Context) error

Flush removes all items from the default cache store.

func (*Manager) Forever

func (m *Manager) Forever(ctx context.Context, key string, value interface{}) error

Forever stores a value in the default cache store indefinitely.

func (*Manager) Forget

func (m *Manager) Forget(ctx context.Context, key string) error

Forget removes a value from the default cache store.

func (*Manager) ForgetMultiple

func (m *Manager) ForgetMultiple(ctx context.Context, keys []string) error

ForgetMultiple removes multiple values from the default cache store.

func (*Manager) Get

func (m *Manager) Get(ctx context.Context, key string) (interface{}, error)

Get retrieves a value from the default cache store.

func (*Manager) GetAs

func (m *Manager) GetAs(ctx context.Context, key string, dest interface{}) error

GetAs retrieves a value and unmarshals it into the provided destination pointer. This provides type-safe retrieval with automatic deserialization.

func (*Manager) GetBool

func (m *Manager) GetBool(ctx context.Context, key string) (bool, error)

GetBool retrieves a bool value from the cache.

func (*Manager) GetFloat64

func (m *Manager) GetFloat64(ctx context.Context, key string) (float64, error)

GetFloat64 retrieves a float64 value from the cache.

func (*Manager) GetInt

func (m *Manager) GetInt(ctx context.Context, key string) (int, error)

GetInt retrieves an int value from the cache.

func (*Manager) GetInt64

func (m *Manager) GetInt64(ctx context.Context, key string) (int64, error)

GetInt64 retrieves an int64 value from the cache.

func (*Manager) GetMultiple

func (m *Manager) GetMultiple(ctx context.Context, keys []string) (map[string]interface{}, error)

GetMultiple retrieves multiple values from the default cache store.

func (*Manager) GetPrefix

func (m *Manager) GetPrefix() string

GetPrefix returns the prefix of the default store.

func (*Manager) GetString

func (m *Manager) GetString(ctx context.Context, key string) (string, error)

GetString retrieves a string value from the cache.

func (*Manager) Has

func (m *Manager) Has(ctx context.Context, key string) (bool, error)

Has checks if a key exists in the default cache store.

func (*Manager) Increment

func (m *Manager) Increment(ctx context.Context, key string, value int64) (int64, error)

Increment increments a value in the default cache store.

func (*Manager) Missing

func (m *Manager) Missing(ctx context.Context, key string) (bool, error)

Missing checks if a key does not exist in the default cache store.

func (*Manager) Pull

func (m *Manager) Pull(ctx context.Context, key string) (interface{}, error)

Pull retrieves a value from the cache and then deletes it.

func (*Manager) Put

func (m *Manager) Put(ctx context.Context, key string, value interface{}, ttl time.Duration) error

Put stores a value in the default cache store.

func (*Manager) PutMultiple

func (m *Manager) PutMultiple(ctx context.Context, items map[string]interface{}, ttl time.Duration) error

PutMultiple stores multiple values in the default cache store.

func (*Manager) RegisterDriver

func (m *Manager) RegisterDriver(name string, factory DriverFactory)

RegisterDriver registers a driver factory for the given driver name.

func (*Manager) RegisterMetrics

func (m *Manager) RegisterMetrics() error

RegisterMetrics registers cache metrics with OpenTelemetry.

func (*Manager) Remember

func (m *Manager) Remember(ctx context.Context, key string, ttl time.Duration, callback func() (interface{}, error)) (interface{}, error)

Remember retrieves a value from the cache or executes the callback and stores the result. This implements the cache-aside pattern.

func (*Manager) RememberForever

func (m *Manager) RememberForever(ctx context.Context, key string, callback func() (interface{}, error)) (interface{}, error)

RememberForever retrieves a value from the cache or executes the callback and stores the result forever.

func (*Manager) SetPrefix

func (m *Manager) SetPrefix(prefix string)

SetPrefix sets the prefix of the default store.

func (*Manager) Stats

func (m *Manager) Stats() cache.Stats

Stats returns the statistics of the default cache store.

func (*Manager) Stop

func (m *Manager) Stop(ctx context.Context) error

Stop stops the cache manager gracefully. This implements the Stoppable interface.

func (*Manager) Store

func (m *Manager) Store(name string) (cache.Store, error)

Store returns the cache store with the given name. If name is empty, returns the default store.

func (*Manager) Tags

func (m *Manager) Tags(tags ...string) cache.TaggedStore

Tags returns a tagged cache store.

type StoreConfig

type StoreConfig struct {
	// Driver is the cache driver name (e.g., "redis", "memory").
	Driver string `mapstructure:"driver"`

	// Connection is the connection name to use (for drivers that support multiple connections).
	Connection string `mapstructure:"connection"`

	// Prefix is the store-specific cache key prefix.
	// This overrides the global prefix for this store.
	Prefix string `mapstructure:"prefix"`

	// Options contains driver-specific configuration options.
	Options map[string]interface{} `mapstructure:"options"`
}

StoreConfig represents the configuration for a single cache store.

func (StoreConfig) Decode

func (c StoreConfig) Decode(target interface{}) error

Decode decodes the store options into the target struct.

Directories

Path Synopsis
drivers
examples

Jump to

Keyboard shortcuts

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