bps-rw/Services/CacheService.cs

139 lines
4.6 KiB
C#

using Microsoft.Extensions.Caching.Memory;
using System.Collections.Concurrent;
using System.Text.RegularExpressions;
namespace BankSampahApp.Services;
/// <summary>
/// Implementation of cache service dengan enhanced functionality
/// </summary>
public class CacheService : ICacheService
{
private readonly IMemoryCache _cache;
private readonly ILogger<CacheService> _logger;
private readonly ConcurrentDictionary<string, bool> _cacheKeys;
public CacheService(IMemoryCache cache, ILogger<CacheService> logger)
{
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_cacheKeys = new ConcurrentDictionary<string, bool>();
}
/// <inheritdoc/>
public async Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> provider, TimeSpan expiration, CacheItemPriority priority = CacheItemPriority.Normal)
{
if (_cache.TryGetValue(key, out T? cachedValue))
{
_logger.LogDebug("Cache hit for key: {CacheKey}", key);
return cachedValue!;
}
_logger.LogDebug("Cache miss for key: {CacheKey}, fetching fresh data", key);
var data = await provider();
Set(key, data, expiration, priority);
return data;
}
/// <inheritdoc/>
public async Task<T> GetOrSetWithSlidingAsync<T>(string key, Func<Task<T>> provider, TimeSpan slidingExpiration, TimeSpan absoluteExpiration, CacheItemPriority priority = CacheItemPriority.Normal)
{
if (_cache.TryGetValue(key, out T? cachedValue))
{
_logger.LogDebug("Cache hit for key: {CacheKey}", key);
return cachedValue!;
}
_logger.LogDebug("Cache miss for key: {CacheKey}, fetching fresh data", key);
var data = await provider();
var cacheOptions = new MemoryCacheEntryOptions
{
SlidingExpiration = slidingExpiration,
AbsoluteExpirationRelativeToNow = absoluteExpiration,
Priority = priority,
PostEvictionCallbacks = { new PostEvictionCallbackRegistration
{
EvictionCallback = (key, value, reason, state) =>
{
_cacheKeys.TryRemove(key.ToString()!, out _);
_logger.LogDebug("Cache entry evicted: {Key}, Reason: {Reason}", key, reason);
}
}}
};
_cache.Set(key, data, cacheOptions);
_cacheKeys.TryAdd(key, true);
_logger.LogDebug("Cached data with sliding expiration for key: {CacheKey}", key);
return data;
}
/// <inheritdoc/>
public T? Get<T>(string key)
{
if (_cache.TryGetValue(key, out T? value))
{
_logger.LogDebug("Retrieved cached value for key: {CacheKey}", key);
return value;
}
_logger.LogDebug("Cache miss for key: {CacheKey}", key);
return default;
}
/// <inheritdoc/>
public void Set<T>(string key, T value, TimeSpan expiration, CacheItemPriority priority = CacheItemPriority.Normal)
{
var cacheOptions = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = expiration,
Priority = priority,
PostEvictionCallbacks = { new PostEvictionCallbackRegistration
{
EvictionCallback = (key, value, reason, state) =>
{
_cacheKeys.TryRemove(key.ToString()!, out _);
_logger.LogDebug("Cache entry evicted: {Key}, Reason: {Reason}", key, reason);
}
}}
};
_cache.Set(key, value, cacheOptions);
_cacheKeys.TryAdd(key, true);
_logger.LogDebug("Cached value for key: {CacheKey}, Expiration: {Expiration}", key, expiration);
}
/// <inheritdoc/>
public void Remove(string key)
{
_cache.Remove(key);
_cacheKeys.TryRemove(key, out _);
_logger.LogDebug("Removed cache entry for key: {CacheKey}", key);
}
/// <inheritdoc/>
public void RemoveByPattern(string pattern)
{
var regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
var keysToRemove = _cacheKeys.Keys.Where(key => regex.IsMatch(key)).ToList();
foreach (var key in keysToRemove)
{
Remove(key);
}
_logger.LogDebug("Removed {Count} cache entries matching pattern: {Pattern}", keysToRemove.Count, pattern);
}
/// <inheritdoc/>
public bool Exists(string key)
{
return _cacheKeys.ContainsKey(key);
}
}