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:
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)
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:
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:
func Key(parts...string) string {
return strings.Join(parts, ":")
}Example:
Key("product", "123") // "product:123"đ§Ź FX Integration (module.go)
We wire the cache layer into Uber FX:
package cacheimport "go.uber.org/fx"var Module = fx.Options(
fx.Provide(NewClient),
fx.Provide(NewStore),)Then in main.go:
fx.New( cache.Module, // other modules...)đ§Ș Example Usage
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:
kvstore.KVStoreThis 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.


đ 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
Storethat 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
