Building a Redis-Backed Cache Layer in Go with Uber FX 🧠

Redis is a powerful tool for caching, session management, and ephemeral storage. But integrating it cleanly into a Go project — especially one using Uber FX — requires thoughtful design. In this article, we’ll walk through how to build a flexible, idiomatic Redis-backed cache layer that supports any type, avoids unnecessary abstractions, and fits naturally into a modular Go architecture.

đŸ§± Project Structure

We start by organizing our cache logic under internal/storage/cache, keeping it clean and domain-agnostic:

text
internal/
├── storage/
│   
├── cache/
│   
│   
├── client.go        // Redis connection setup
│   
│   
├──
store.go         // Cache logic (set/get/delete)
│   
│   
└── module.go        // Uber FX integration

🔌 Redis Client Setup (client.go)

go
package cacheimport "github.com/redis/go-redis/v9"func NewClient() *redis.Client {
    return redis.NewClient(&redis.Options{
        Addr: "localhost:6379", DB: 0,
    })
}

This sets up a Redis client using the official go-redis library.

đŸ§© Generic Cache Store (store.go)

We define a Store type that can cache any serializable Go value using JSON:

go
package cache
import (
"context"
"encoding/json"
"strings"
"time"
"github.com/redis/go-redis/v9"
)
type Store struct {
    client *redis.Client
}
func NewStore(client *redis.Client) *Store {
    return &Store{
        client: client
    }
}
func (s *Store) Set(ctx context.Context, key string, value interface{
}, ttl time.Duration) error {
    data, err := json.Marshal(value)
    if err != nil {
        return err
    }
    return s.client.Set(ctx, key, data, ttl).Err()
}
func (s *Store) Get(ctx context.Context, key string, dest interface{
}) error {
    data, err := s.client.Get(ctx, key).Bytes()
    if err != nil {
        return err
    }
    return json.Unmarshal(data, dest)
}
func (s *Store) Delete(ctx context.Context, key string) error {
    return s.client.Del(ctx, key).Err()
}
func (s *Store) Exists(ctx context.Context, key string) (bool, error) {
    count, err := s.client.Exists(ctx, key).Result()
    return count > 0, err
}

🧠 Key Naming Helper

To keep Redis keys consistent and domain-aligned, we use a simple helper:

go
func Key(parts...string) string {
    return strings.Join(parts, ":")
}

Example:

go
Key("product", "123") // "product:123"

🧬 FX Integration (module.go)

We wire the cache layer into Uber FX:

go
package cacheimport "go.uber.org/fx"var Module = fx.Options(
fx.Provide(NewClient),
fx.Provide(NewStore),)

Then in main.go:

go
fx.New(    cache.Module,    // other modules...)

đŸ§Ș Example Usage

go
type Product struct {
    ID string
    Name string
    Price float64
}
var p Productif err := store.Get(ctx, Key("product", "123"), &p);
err == nil {
    return p, nil
}

đŸ§Ÿ Naming Strategy

To avoid repetition like cache.CacheStoreWe renamed the folder and typed:

  • Folder: kvstore
  • Type: KVStore

Resulting usage:

text
kvstore.KVStore

This keeps naming clean, idiomatic, and extensible.

Git commit

Go-simple

✅ Summary

This Redis-backed cache layer supports:

  • Generic type-safe storage
  • TTL control
  • Domain-aligned key naming
  • Clean FX lifecycle integration
  • Idiomatic naming and modularity

It’s ready for production and extensible for metrics, health checks, and pub/sub. Whether you’re caching products, sessions, or search results, this design keeps your Go code clean, maintainable, and scalable.

Building a Redis-Backed Cache Layer in Go with Uber FX 🧠
Building a Redis-Backed Cache Layer in Go with Uber FX 🧠

🏁 Conclusion

Designing a Redis-backed cache layer in Go doesn’t have to be complicated. By focusing on idiomatic patterns, modular structure, and Uber FX integration, we’ve built a system that’s clean, extensible, and production-ready. Whether you’re caching products, sessions, or search results, this approach gives you:

  • 🔧 A flexible Store that handles any type via JSON
  • 🧠 Predictable key naming for domain clarity
  • âšĄïž TTL support and cache invalidation
  • 🧬 Seamless FX lifecycle wiring
  • 📩 Scalable structure for future modules