Key-Value Store
The KV module provides Redis-compatible key-value storage for caching, sessions, and temporary data.
Quick Start
import "github.com/awesome-goose/goose/modules/kv"
// Configure the module
kvModule := kv.NewModule(
kv.WithDriver("redis"),
kv.WithHost("localhost"),
kv.WithPort(6379),
)
// Include in application
stop, err := goose.Start(goose.API(platform, module, []types.Module{
kvModule,
}))
Configuration Options
Redis
kvModule := kv.NewModule(
kv.WithDriver("redis"),
kv.WithHost("localhost"),
kv.WithPort(6379),
kv.WithPassword(""),
kv.WithDB(0),
)
In-Memory (Development)
kvModule := kv.NewModule(
kv.WithDriver("memory"),
)
Environment Configuration
kvModule := kv.NewModule(
kv.WithDriver(env.String("KV_DRIVER", "redis")),
kv.WithHost(env.String("REDIS_HOST", "localhost")),
kv.WithPort(env.Int("REDIS_PORT", 6379)),
kv.WithPassword(env.String("REDIS_PASSWORD", "")),
)
.env file:
KV_DRIVER=redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
Injecting KV Client
Services receive the KV client via dependency injection:
import "github.com/awesome-goose/goose/modules/kv"
type CacheService struct {
kv *kv.Client `inject:""`
}
Basic Operations
Set Value
func (s *CacheService) Set(key string, value interface{}) error {
return s.kv.Set(key, value)
}
Set with Expiration (TTL)
func (s *CacheService) SetWithTTL(key string, value interface{}, ttl time.Duration) error {
return s.kv.SetEx(key, value, ttl)
}
// Example: Cache for 1 hour
s.kv.SetEx("user:123", user, time.Hour)
// Cache for 5 minutes
s.kv.SetEx("search:results:query", results, 5*time.Minute)
Get Value
func (s *CacheService) Get(key string) (string, error) {
return s.kv.Get(key)
}
// Get with type conversion
func (s *CacheService) GetUser(id string) (*User, error) {
data, err := s.kv.Get("user:" + id)
if err != nil {
return nil, err
}
var user User
err = json.Unmarshal([]byte(data), &user)
return &user, err
}
Delete Value
func (s *CacheService) Delete(key string) error {
return s.kv.Del(key)
}
// Delete multiple keys
func (s *CacheService) DeleteMany(keys ...string) error {
return s.kv.Del(keys...)
}
Check Existence
func (s *CacheService) Exists(key string) bool {
exists, _ := s.kv.Exists(key)
return exists
}
Caching Patterns
Cache-Aside
func (s *ProductService) GetByID(id string) (*Product, error) {
cacheKey := "product:" + id
// Try cache first
cached, err := s.kv.Get(cacheKey)
if err == nil {
var product Product
json.Unmarshal([]byte(cached), &product)
return &product, nil
}
// Fetch from database
var product Product
if err := s.db.First(&product, "id = ?", id).Error; err != nil {
return nil, err
}
// Store in cache
data, _ := json.Marshal(product)
s.kv.SetEx(cacheKey, string(data), time.Hour)
return &product, nil
}
Write-Through
func (s *ProductService) Update(id string, dto UpdateProductDTO) (*Product, error) {
// Update database
product, err := s.updateInDB(id, dto)
if err != nil {
return nil, err
}
// Update cache
cacheKey := "product:" + id
data, _ := json.Marshal(product)
s.kv.SetEx(cacheKey, string(data), time.Hour)
return product, nil
}
Cache Invalidation
func (s *ProductService) Delete(id string) error {
// Delete from database
if err := s.db.Delete(&Product{}, "id = ?", id).Error; err != nil {
return err
}
// Invalidate cache
s.kv.Del("product:" + id)
// Invalidate related caches
s.kv.Del("products:list")
return nil
}
Session Management
Store user sessions:
type SessionService struct {
kv *kv.Client `inject:""`
}
func (s *SessionService) Create(userID string) (string, error) {
sessionID := uuid.New().String()
session := map[string]interface{}{
"user_id": userID,
"created_at": time.Now(),
}
data, _ := json.Marshal(session)
err := s.kv.SetEx("session:"+sessionID, string(data), 24*time.Hour)
return sessionID, err
}
func (s *SessionService) Get(sessionID string) (*Session, error) {
data, err := s.kv.Get("session:" + sessionID)
if err != nil {
return nil, err
}
var session Session
json.Unmarshal([]byte(data), &session)
return &session, nil
}
func (s *SessionService) Destroy(sessionID string) error {
return s.kv.Del("session:" + sessionID)
}
func (s *SessionService) Refresh(sessionID string) error {
return s.kv.Expire("session:"+sessionID, 24*time.Hour)
}
Rate Limiting
Implement rate limiting with KV:
type RateLimiter struct {
kv *kv.Client `inject:""`
}
func (r *RateLimiter) IsAllowed(key string, limit int, window time.Duration) bool {
countKey := "ratelimit:" + key
// Get current count
countStr, err := r.kv.Get(countKey)
if err != nil {
// First request
r.kv.SetEx(countKey, "1", window)
return true
}
count, _ := strconv.Atoi(countStr)
if count >= limit {
return false
}
// Increment
r.kv.Incr(countKey)
return true
}
Counter Operations
Increment/Decrement
func (s *StatsService) IncrementViews(postID string) (int64, error) {
return s.kv.Incr("views:" + postID)
}
func (s *StatsService) DecrementStock(productID string) (int64, error) {
return s.kv.Decr("stock:" + productID)
}
func (s *StatsService) IncrementBy(key string, amount int64) (int64, error) {
return s.kv.IncrBy(key, amount)
}
Hash Operations
Store structured data:
// Set hash field
func (s *UserService) SetProfile(userID string, field string, value string) error {
return s.kv.HSet("user:"+userID+":profile", field, value)
}
// Get hash field
func (s *UserService) GetProfileField(userID string, field string) (string, error) {
return s.kv.HGet("user:"+userID+":profile", field)
}
// Get all hash fields
func (s *UserService) GetFullProfile(userID string) (map[string]string, error) {
return s.kv.HGetAll("user:" + userID + ":profile")
}
List Operations
Implement queues:
// Push to list
func (s *QueueService) Push(queue string, value string) error {
return s.kv.LPush(queue, value)
}
// Pop from list
func (s *QueueService) Pop(queue string) (string, error) {
return s.kv.RPop(queue)
}
// Get list length
func (s *QueueService) Length(queue string) (int64, error) {
return s.kv.LLen(queue)
}
Set Operations
Track unique items:
// Add to set
func (s *TagService) AddTag(postID string, tag string) error {
return s.kv.SAdd("post:"+postID+":tags", tag)
}
// Get all set members
func (s *TagService) GetTags(postID string) ([]string, error) {
return s.kv.SMembers("post:" + postID + ":tags")
}
// Check membership
func (s *TagService) HasTag(postID string, tag string) (bool, error) {
return s.kv.SIsMember("post:"+postID+":tags", tag)
}
Key Management
Key Patterns
Use consistent key naming:
// Pattern: resource:id:attribute
"user:123" // User data
"user:123:profile" // User profile hash
"user:123:sessions" // User sessions set
"product:456" // Product data
"cache:products:list" // Cached product list
"session:abc123" // Session data
"ratelimit:ip:192.168.1.1" // Rate limit counter
Key Expiration
// Set expiration
s.kv.Expire("temp:data", time.Hour)
// Get TTL
ttl, _ := s.kv.TTL("temp:data")
// Remove expiration
s.kv.Persist("temp:data")
Pattern Matching
// Get keys matching pattern
keys, _ := s.kv.Keys("user:*")
// Delete by pattern (use with caution)
for _, key := range keys {
s.kv.Del(key)
}
Best Practices
- Use namespaced keys with consistent patterns
- Set TTL for cached data to prevent stale reads
- Handle missing keys gracefully
- Use appropriate data structures (strings, hashes, lists, sets)
- Avoid storing large values - prefer references
- Monitor memory usage in production
Error Handling
func (s *CacheService) GetSafe(key string) (string, bool) {
value, err := s.kv.Get(key)
if err != nil {
// Key doesn't exist or error
return "", false
}
return value, true
}
Next Steps
- SQL Database - Relational database
- Caching - Advanced caching
- Sessions - Session management