- Add metrics package tracking cache hit-rate, API quota, circuit breaker trips, search time - Integrate metrics with cache layer, yandex client, and API handlers - Add /metrics HTTP endpoint for Prometheus-compatible metrics exposure - Write tests for metrics functionality across cache, yandex, and API handlers - Update test files to support new metrics infrastructure
93 lines
2.9 KiB
Go
93 lines
2.9 KiB
Go
package routing
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"trip-planner/internal/cache"
|
|
"trip-planner/internal/metrics"
|
|
"trip-planner/internal/yandex"
|
|
)
|
|
|
|
// SearchCacheService handles caching and on-demand Yandex /search calls.
|
|
type SearchCacheService struct {
|
|
cache *cache.CacheAside
|
|
yclient *yandex.Client
|
|
metrics *metrics.Metrics
|
|
}
|
|
|
|
// NewSearchCacheService creates a new search cache service.
|
|
func NewSearchCacheService(cacheStore cache.Cache, yclient *yandex.Client, m *metrics.Metrics) *SearchCacheService {
|
|
return &SearchCacheService{
|
|
cache: cache.NewCacheAside(cacheStore, m),
|
|
yclient: yclient,
|
|
metrics: m,
|
|
}
|
|
}
|
|
|
|
// SearchWithCache performs a route search with caching support.
|
|
// It uses the cache-aside pattern: try cache first, then Yandex API, then write back to cache.
|
|
func (s *SearchCacheService) SearchWithCache(ctx context.Context, from, to, date string, opts SearchOptions) (*Itinerary, error) {
|
|
// Generate cache key
|
|
searchKey := cache.GetSearchKey(from, to, date)
|
|
|
|
// Try to get from cache first
|
|
fetchFunc := func() ([]byte, error) {
|
|
// If we reach here, it's a cache miss - perform on-demand Yandex /search call
|
|
return s.performYandexSearch(ctx, from, to, date, opts)
|
|
}
|
|
|
|
// Get or set from cache with appropriate TTL based on far-term flag
|
|
isFarTerm := opts.FarTerm
|
|
data, err := s.cache.GetSearch(ctx, searchKey, fetchFunc, isFarTerm)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("search cache get/set: %w", err)
|
|
}
|
|
|
|
// Parse the itinerary from cached data (assuming JSON format)
|
|
var result Itinerary
|
|
if err := parseItineraryFromBytes(data, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse itinerary from cache: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// performYandexSearch makes the actual Yandex /search API call.
|
|
func (s *SearchCacheService) performYandexSearch(ctx context.Context, from, to, date string, opts SearchOptions) ([]byte, error) {
|
|
// Build query parameters for Yandex /search endpoint
|
|
query := map[string]string{
|
|
"from": from,
|
|
"to": to,
|
|
"date": date,
|
|
}
|
|
|
|
// Execute the Yandex API request
|
|
resp, err := s.yclient.Do(ctx, "GET", "/v3.0/search/", query)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("yandex search failed: %w", err)
|
|
}
|
|
|
|
// Convert response to bytes for caching
|
|
return convertResponseToBytes(resp)
|
|
}
|
|
|
|
// parseItineraryFromBytes parses an itinerary from byte data.
|
|
func parseItineraryFromBytes(data []byte, result *Itinerary) error {
|
|
// This is a placeholder - in real implementation, this would parse JSON
|
|
// into the Itinerary struct
|
|
if len(data) == 0 {
|
|
return fmt.Errorf("empty data")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// convertResponseToBytes converts Yandex API response to bytes for caching.
|
|
func convertResponseToBytes(resp *yandex.Response) ([]byte, error) {
|
|
// This is a placeholder - in real implementation, this would serialize the response
|
|
if resp == nil {
|
|
return nil, fmt.Errorf("nil response")
|
|
}
|
|
return []byte(`{"search":{"from":"%s","to":"%s"},"segments":[]}`), nil
|
|
}
|