data layer
This commit is contained in:
87
data-layer/api/internal/service/query_service.go
Normal file
87
data-layer/api/internal/service/query_service.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// Package service holds business logic. It owns cache orchestration around
|
||||
// the read repos and never touches HTTP/chi or the SQL drivers directly.
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/dbiz/cdp/data-layer/api/internal/apperr"
|
||||
"github.com/dbiz/cdp/data-layer/api/internal/cache"
|
||||
"github.com/dbiz/cdp/data-layer/api/internal/model"
|
||||
"github.com/dbiz/cdp/data-layer/api/internal/repo"
|
||||
)
|
||||
|
||||
type QueryService struct {
|
||||
events *repo.EventRepo
|
||||
analytics *repo.AnalyticsRepo
|
||||
cache *cache.Cache
|
||||
queryTTL time.Duration
|
||||
log *zap.Logger
|
||||
}
|
||||
|
||||
func NewQueryService(events *repo.EventRepo, analytics *repo.AnalyticsRepo, c *cache.Cache, queryTTL time.Duration, log *zap.Logger) *QueryService {
|
||||
return &QueryService{events: events, analytics: analytics, cache: c, queryTTL: queryTTL, log: log}
|
||||
}
|
||||
|
||||
// cached wraps `fetch` with the per-workspace Redis cache. Result is JSON-
|
||||
// encoded on miss; CacheHit is set true on hit.
|
||||
func (s *QueryService) cached(
|
||||
ctx context.Context,
|
||||
kind, workspaceID string,
|
||||
params any,
|
||||
fetch func(context.Context) (*model.QueryResult, error),
|
||||
) (*model.QueryResult, error) {
|
||||
key, err := cache.Key(kind, workspaceID, params)
|
||||
if err != nil {
|
||||
return nil, apperr.Internal(err)
|
||||
}
|
||||
if cached, ok := s.cache.Get(ctx, key); ok {
|
||||
var out model.QueryResult
|
||||
if jerr := json.Unmarshal(cached, &out); jerr == nil {
|
||||
out.CacheHit = true
|
||||
return &out, nil
|
||||
}
|
||||
}
|
||||
start := time.Now()
|
||||
res, err := fetch(ctx)
|
||||
if err != nil {
|
||||
return nil, apperr.Internal(err)
|
||||
}
|
||||
res.DurationMS = time.Since(start).Milliseconds()
|
||||
res.CacheHit = false
|
||||
|
||||
if b, jerr := json.Marshal(res); jerr == nil {
|
||||
if cerr := s.cache.Set(ctx, key, b, s.queryTTL); cerr != nil {
|
||||
s.log.Warn("cache set", zap.String("key", key), zap.Error(cerr))
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *QueryService) Events(ctx context.Context, q model.EventQuery) (*model.QueryResult, error) {
|
||||
return s.cached(ctx, "query:events", q.WorkspaceID, q, func(c context.Context) (*model.QueryResult, error) {
|
||||
return s.events.QueryEvents(c, q)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *QueryService) Funnel(ctx context.Context, q repo.FunnelQuery) (*model.QueryResult, error) {
|
||||
return s.cached(ctx, "query:funnel", q.WorkspaceID, q, func(c context.Context) (*model.QueryResult, error) {
|
||||
return s.analytics.Funnel(c, q)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *QueryService) Retention(ctx context.Context, q repo.RetentionQuery) (*model.QueryResult, error) {
|
||||
return s.cached(ctx, "query:retention", q.WorkspaceID, q, func(c context.Context) (*model.QueryResult, error) {
|
||||
return s.analytics.Retention(c, q)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *QueryService) Sessions(ctx context.Context, q repo.SessionQuery) (*model.QueryResult, error) {
|
||||
return s.cached(ctx, "query:session", q.WorkspaceID, q, func(c context.Context) (*model.QueryResult, error) {
|
||||
return s.analytics.Sessions(c, q)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user