package service import ( "fmt" "log" "sync" "sync/atomic" ) type CacheStats struct { Hits int64 Misses int64 Size int } type CachedGeocoder struct { underlying GeocodingService cache map[string]string mutex sync.RWMutex hits int64 misses int64 } func NewCachedGeocodingService(underlying GeocodingService) *CachedGeocoder { return &CachedGeocoder{ underlying: underlying, cache: make(map[string]string), } } func (g *CachedGeocoder) GetAddressFromCoordinates(latitude, longitude float64) (string, error) { cacheKey := fmt.Sprintf("%.6f,%.6f", latitude, longitude) // Check cache first g.mutex.RLock() if address, ok := g.cache[cacheKey]; ok { g.mutex.RUnlock() atomic.AddInt64(&g.hits, 1) log.Printf("CACHE HIT: Coordinates (%.6f,%.6f) found in cache", latitude, longitude) return address, nil } g.mutex.RUnlock() atomic.AddInt64(&g.misses, 1) // Not in cache, call the underlying service address, err := g.underlying.GetAddressFromCoordinates(latitude, longitude) if err != nil { return "", err } // Cache the result g.mutex.Lock() g.cache[cacheKey] = address g.mutex.Unlock() log.Printf("CACHE UPDATE: Coordinates (%.6f,%.6f) added to cache", latitude, longitude) return address, nil } // GetStats returns the current cache statistics func (g *CachedGeocoder) GetStats() CacheStats { g.mutex.RLock() stats := CacheStats{ Hits: g.hits, Misses: g.misses, Size: len(g.cache), } g.mutex.RUnlock() return stats } // ClearCache empties the cache func (g *CachedGeocoder) ClearCache() { g.mutex.Lock() g.cache = make(map[string]string) g.mutex.Unlock() }