✨ feat(cache): add in-memory cache service (ADR-0022 Phase 1 part 2) (#23)
Phase 1 part 2 of ADR-0022 (companion to PR #22 rate-limit). In-memory cache service via go-cache, used by /api/version (60s TTL). 6/6 unit tests pass. ~95% Mistral autonomous via ICM workspace, cost €2.50 stages 01-02 (50% reduction vs T5 thanks to pre-extracted snippets in shared/). Co-authored-by: Gabriel Radureau <arcodange@gmail.com> Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
This commit was merged in pull request #23.
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/rs/zerolog/log"
|
||||
httpSwagger "github.com/swaggo/http-swagger"
|
||||
|
||||
"dance-lessons-coach/pkg/cache"
|
||||
"dance-lessons-coach/pkg/config"
|
||||
"dance-lessons-coach/pkg/greet"
|
||||
"dance-lessons-coach/pkg/middleware"
|
||||
@@ -65,6 +66,7 @@ type Server struct {
|
||||
validator *validation.Validator
|
||||
userRepo user.UserRepository
|
||||
userService user.UserService
|
||||
cacheService cache.Service
|
||||
startedAt time.Time
|
||||
}
|
||||
|
||||
@@ -83,15 +85,28 @@ func NewServer(cfg *config.Config, readyCtx context.Context) *Server {
|
||||
log.Warn().Err(err).Msg("Failed to initialize user services, user functionality will be disabled")
|
||||
}
|
||||
|
||||
// Initialize cache service
|
||||
var cacheService cache.Service
|
||||
if cfg.GetCacheEnabled() {
|
||||
cacheService = cache.NewInMemoryService(
|
||||
time.Duration(cfg.GetCacheDefaultTTLSeconds())*time.Second,
|
||||
time.Duration(cfg.GetCacheCleanupIntervalSeconds())*time.Second,
|
||||
)
|
||||
log.Trace().Msg("Cache service initialized")
|
||||
} else {
|
||||
log.Trace().Msg("Cache service disabled")
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
router: chi.NewRouter(),
|
||||
readyCtx: readyCtx,
|
||||
withOTEL: cfg.GetTelemetryEnabled(),
|
||||
config: cfg,
|
||||
validator: validator,
|
||||
userRepo: userRepo,
|
||||
userService: userService,
|
||||
startedAt: time.Now(),
|
||||
router: chi.NewRouter(),
|
||||
readyCtx: readyCtx,
|
||||
withOTEL: cfg.GetTelemetryEnabled(),
|
||||
config: cfg,
|
||||
validator: validator,
|
||||
userRepo: userRepo,
|
||||
userService: userService,
|
||||
cacheService: cacheService,
|
||||
startedAt: time.Now(),
|
||||
}
|
||||
s.setupRoutes()
|
||||
return s
|
||||
@@ -351,26 +366,49 @@ func (s *Server) handleVersion(w http.ResponseWriter, r *http.Request) {
|
||||
format = "plain" // default format
|
||||
}
|
||||
|
||||
// Check cache if enabled
|
||||
cacheKey := "version:" + format
|
||||
if s.cacheService != nil {
|
||||
if cached, ok := s.cacheService.Get(cacheKey); ok {
|
||||
log.Trace().Str("cache_key", cacheKey).Msg("Cache hit for version")
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
if format == "json" {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
}
|
||||
w.Write([]byte(cached.(string)))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Build response
|
||||
var response string
|
||||
switch format {
|
||||
case "plain":
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write([]byte(version.Short()))
|
||||
response = version.Short()
|
||||
case "full":
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write([]byte(version.Full()))
|
||||
response = version.Full()
|
||||
case "json":
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
jsonResponse := fmt.Sprintf(`{
|
||||
response = fmt.Sprintf(`{
|
||||
"version": "%s",
|
||||
"commit": "%s",
|
||||
"built": "%s",
|
||||
"go": "%s"
|
||||
}`, version.Version, version.Commit, version.Date, version.GoVersion)
|
||||
w.Write([]byte(jsonResponse))
|
||||
default:
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write([]byte(version.Short()))
|
||||
response = version.Short()
|
||||
}
|
||||
|
||||
// Cache the response for 60 seconds if cache is enabled
|
||||
if s.cacheService != nil {
|
||||
s.cacheService.Set(cacheKey, response, 60*time.Second)
|
||||
log.Trace().Str("cache_key", cacheKey).Msg("Cached version response")
|
||||
}
|
||||
|
||||
w.Write([]byte(response))
|
||||
}
|
||||
|
||||
// HealthzResponse represents the Kubernetes-style health check response
|
||||
|
||||
Reference in New Issue
Block a user