cache

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 6 Imported by: 0

README

go-cache

A lightweight Go cache library built around a backend-agnostic Cache interface, making it easy to switch cache backends as your application grows.

Redis is the only backend implemented right now, but the interface was designed with more backends in mind - Memcached, DragonflyDB, an in-memory store, whatever comes next. Write your application against Cache, not against Redis, and you won't have to touch that code when a new backend shows up.

Features

  • Backend-agnostic API via the Cache interface
  • Redis backend with automatic connection verification
  • TTL support for expiring entries
  • JSON helpers for storing and retrieving Go structs
  • Distributed locking for coordinating work across processes
  • Configurable key prefixing to keep your data isolated

Installation

go get go.analike.dev/cache

Why depend on the interface, not Redis

NewRedis returns a Cache, not a *Redis. That's on purpose - it's what makes swapping backends later a non-event.

cacheStore, err := cache.NewRedis(redisConfig)

A service that takes a Cache doesn't need to know or care what's behind it:

type UserService struct {
	cache cache.Cache
}

func NewUserService(c cache.Cache) *UserService {
	return &UserService{cache: c}
}

If a Memcached or in-memory backend gets added down the line, UserService doesn't change at all.

Getting Started

package main

import (
	"fmt"
	"time"

	"go.analike.dev/cache"
)

func main() {
	redisConfig := cache.RedisConfig{
		Address:        "127.0.0.1:6379",
		Password:       "",
		Database:       5,
		Prefix:         "myapp:",
		ConnectTimeout: 3 * time.Second,
		ReadTimeout:    5 * time.Second,
		WriteTimeout:   7 * time.Second,
	}

	cacheStore, err := cache.NewRedis(redisConfig)
	if err != nil {
		fmt.Println("Failed to initialize cache:", err)
		return
	}
	defer cacheStore.Close()

	fmt.Println("Connected to cache")
}

Basic Operations

Store a value
err := cacheStore.Set("user:123", "John Doe", 10*time.Minute)
if err != nil {
	fmt.Println("Failed to store value:", err)
	return
}
Retrieve a value
value, err := cacheStore.Get("user:123")
if err != nil {
	fmt.Println("Failed to retrieve value:", err)
	return
}

fmt.Println(value)

JSON Operations

For structs, use SetJSON and GetJSON instead of marshaling by hand:

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

user := User{
	ID:    123,
	Name:  "John Doe",
	Email: "john@example.com",
}

err := cacheStore.SetJSON("user:123", user, 10*time.Minute)
if err != nil {
	fmt.Println("Failed to store user:", err)
	return
}

var cachedUser User

err = cacheStore.GetJSON("user:123", &cachedUser)
if err != nil {
	fmt.Println("Failed to retrieve user:", err)
	return
}

fmt.Printf("%+v\n", cachedUser)

Distributed Locking

Use this when you need to guarantee only one process runs a critical section at a time - a cron job across replicas, a batch task, anything that shouldn't run twice at once.

lockKey := "locks:daily-job"
lockToken := randomString()

locked, err := cacheStore.Lock(lockKey, lockToken, 30*time.Second)
if err != nil {
	fmt.Println("Failed to acquire lock:", err)
	return
}

if !locked {
	fmt.Println("Lock is already held")
	return
}

defer func() {
	if err := cacheStore.Unlock(lockKey, lockToken); err != nil {
		fmt.Println("Failed to release lock:", err)
	}
}()

// Perform protected work...

Redis Configuration

Field Description
Address Redis server address (host:port).
Password Redis password, if authentication is enabled.
Database Redis database index.
Prefix Prefix automatically prepended to every key.
ConnectTimeout Timeout for establishing a connection.
ReadTimeout Read operation timeout.
WriteTimeout Write operation timeout.

Roadmap

Redis is the only backend today. Memcached, DragonflyDB, and an in-memory implementation are candidates for later - nothing scheduled yet, but the Cache interface is what makes adding them low-risk when the time comes.

NewRedis() stays NewRedis() rather than becoming a generic New(). It reads better once there's a NewMemcached() or NewMemory() sitting next to it, so there's no reason to rename it now and break callers later.

Contributing

Issues and pull requests are welcome.

License

MIT. See LICENSE for details.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client interface {
	Get(key string) (string, error)
	Set(key string, value string, ttl time.Duration) error
	SetJSON(key string, value any, ttl time.Duration) error
	GetJSON(key string, target any) error
	Lock(key, token string, ttl time.Duration) (bool, error)
	Unlock(key, token string) error
	Delete(keys ...string) error
	Has(keys ...string) bool
	Close() error
}

func NewRedis

func NewRedis(cnf RedisConfig) (Client, error)

NewRedis initializes a new connection with the underlying go-redis and verifies connectivity.

Returns a (Client, nil) if connectivity test succeeds; otherwise (nil, error)

type RedisClient

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

RedisClient wraps the official go-redis client to provide streamlined helper methods.

func (*RedisClient) Close

func (r *RedisClient) Close() error

Close gracefully closes the Redis client connection pool.

func (*RedisClient) Delete

func (r *RedisClient) Delete(keys ...string) error

Delete removes one or multiple keys from Redis.

func (*RedisClient) Get

func (r *RedisClient) Get(key string) (string, error)

Get retrieves a raw string value from Redis. Returns an error if the key does not exist.

func (*RedisClient) GetJSON

func (r *RedisClient) GetJSON(key string, target any) error

GetJSON retrieves a JSON-byte-slice from Redis and unmarshals it into `target`.

`target` must be a pointer to the target interface

Returns error (redis communication error, key not exist or JSON unmarshal fails)

func (*RedisClient) Has

func (r *RedisClient) Has(keys ...string) bool

Has checks if the key exists.

Returns false if key not found or error occurred; otherwise true.

func (*RedisClient) Lock

func (r *RedisClient) Lock(key, token string, ttl time.Duration) (bool, error)

Lock creates a specific key if not already set and sets the value to `token`.

`token` is more like a password for unlocking. Where if not exact match, unlock fails

Returns bool (lock granted), error (any error that occured)

func (*RedisClient) Set

func (r *RedisClient) Set(key string, value string, ttl time.Duration) error

Set stores a raw string value with an optional Time-To-Live (TTL) expiration.

func (*RedisClient) SetJSON

func (r *RedisClient) SetJSON(key string, value any, ttl time.Duration) error

SetJSON serializes a Go struct into JSON and stores it in Redis.

`value` may either be a pointer or value

Returns error is json-marshal fails or redis encountered an error with the operation

func (*RedisClient) Unlock

func (r *RedisClient) Unlock(key, token string) error

Unlock releases the lock on the specified key

Returns error if an error occurred or the unlock op fails (either token mismatch or non-existent key)

type RedisConfig

type RedisConfig struct {
	// Address is the address formated as host:port
	Address string
	// Password holds the password for authentication if enabled.
	Password string
	// Database is the database to be selected after connecting to the server.
	Database       int
	Prefix         string
	ConnectTimeout time.Duration
	ReadTimeout    time.Duration
	WriteTimeout   time.Duration
}

Jump to

Keyboard shortcuts

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