dukcapil/internal/processor/cached_user_processor.go
2025-09-08 12:24:37 +07:00

113 lines
2.7 KiB
Go

package processor
import (
"context"
"sync"
"time"
"eslogad-be/internal/contract"
"eslogad-be/internal/repository"
"eslogad-be/internal/transformer"
"github.com/google/uuid"
)
// CachedUserProcessor wraps UserProcessor with caching for frequently accessed users
type CachedUserProcessor struct {
userRepo *repository.UserRepositoryImpl
profileRepo *repository.UserProfileRepository
cache map[uuid.UUID]*cacheEntry
mu sync.RWMutex
ttl time.Duration
}
type cacheEntry struct {
user *contract.UserResponse
expiresAt time.Time
}
func NewCachedUserProcessor(userRepo *repository.UserRepositoryImpl, profileRepo *repository.UserProfileRepository) *CachedUserProcessor {
return &CachedUserProcessor{
userRepo: userRepo,
profileRepo: profileRepo,
cache: make(map[uuid.UUID]*cacheEntry),
ttl: 5 * time.Minute, // Cache for 5 minutes
}
}
func (p *CachedUserProcessor) GetUserByIDCached(ctx context.Context, id uuid.UUID) (*contract.UserResponse, error) {
p.mu.RLock()
if entry, exists := p.cache[id]; exists {
if entry.expiresAt.After(time.Now()) {
p.mu.RUnlock()
return entry.user, nil
}
}
p.mu.RUnlock()
// Not in cache or expired, fetch from database using the light method
user, err := p.userRepo.GetByIDLight(ctx, id)
if err != nil {
return nil, err
}
// Convert to contract response
resp := &contract.UserResponse{
ID: user.ID,
Email: user.Email,
Name: user.Name,
IsActive: user.IsActive,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
}
// Store in cache
p.mu.Lock()
p.cache[id] = &cacheEntry{
user: resp,
expiresAt: time.Now().Add(p.ttl),
}
p.mu.Unlock()
// Clean expired entries periodically
go p.cleanExpiredEntries()
return resp, nil
}
// GetUserByIDFull retrieves full user with all relationships - no caching
func (p *CachedUserProcessor) GetUserByIDFull(ctx context.Context, id uuid.UUID) (*contract.UserResponse, error) {
user, err := p.userRepo.GetByID(ctx, id)
if err != nil {
return nil, err
}
resp := transformer.EntityToContract(user)
if resp != nil {
if roles, err := p.userRepo.GetRolesByUserID(ctx, resp.ID); err == nil {
resp.Roles = transformer.RolesToContract(roles)
}
}
return resp, nil
}
// InvalidateCache removes a user from cache
func (p *CachedUserProcessor) InvalidateCache(userID uuid.UUID) {
p.mu.Lock()
delete(p.cache, userID)
p.mu.Unlock()
}
// cleanExpiredEntries removes expired cache entries
func (p *CachedUserProcessor) cleanExpiredEntries() {
p.mu.Lock()
defer p.mu.Unlock()
now := time.Now()
for id, entry := range p.cache {
if entry.expiresAt.Before(now) {
delete(p.cache, id)
}
}
}