using Microsoft.Extensions.Caching.Memory;
namespace BankSampahApp.Services;
///
/// Base service class yang menyediakan functionality umum untuk semua services
/// Menerapkan prinsip DRY dengan mengekstrak common patterns
///
public abstract class BaseService
{
protected readonly ILogger _logger;
protected readonly IMemoryCache _cache;
protected readonly IConfiguration _configuration;
///
/// Constructor untuk base service
///
/// Logger instance
/// Memory cache instance
/// Configuration instance
protected BaseService(ILogger logger, IMemoryCache cache, IConfiguration configuration)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
}
///
/// Executes a function with comprehensive error handling and logging
///
/// Return type
/// Function to execute
/// Name of the operation for logging
/// Fallback value if operation fails
/// Result of operation or fallback value
protected async Task ExecuteWithErrorHandlingAsync(
Func> operation,
string operationName,
T? fallbackValue = default)
{
try
{
_logger.LogInformation("Starting operation: {OperationName}", operationName);
var result = await operation();
_logger.LogInformation("Successfully completed operation: {OperationName}", operationName);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in operation: {OperationName}", operationName);
if (fallbackValue is not null)
{
_logger.LogWarning("Returning fallback value for operation: {OperationName}", operationName);
return fallbackValue;
}
throw;
}
}
///
/// Gets or sets cached data with configurable expiration
///
/// Type of cached data
/// Cache key
/// Function to get data if not cached
/// Cache expiration time
/// Cache priority
/// Cached or fresh data
protected async Task GetOrSetCacheAsync(
string cacheKey,
Func> dataProvider,
TimeSpan expiration,
CacheItemPriority priority = CacheItemPriority.Normal)
{
if (_cache.TryGetValue(cacheKey, out T? cachedValue))
{
_logger.LogDebug("Retrieved data from cache with key: {CacheKey}", cacheKey);
return cachedValue!;
}
_logger.LogDebug("Cache miss for key: {CacheKey}, fetching fresh data", cacheKey);
var data = await dataProvider();
var cacheOptions = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = expiration,
Priority = priority
};
_cache.Set(cacheKey, data, cacheOptions);
_logger.LogDebug("Cached data with key: {CacheKey} for {Expiration}", cacheKey, expiration);
return data;
}
///
/// Gets or sets cached data with sliding expiration
///
/// Type of cached data
/// Cache key
/// Function to get data if not cached
/// Sliding expiration time
/// Absolute expiration time
/// Cache priority
/// Cached or fresh data
protected async Task GetOrSetCacheWithSlidingAsync(
string cacheKey,
Func> dataProvider,
TimeSpan slidingExpiration,
TimeSpan absoluteExpiration,
CacheItemPriority priority = CacheItemPriority.Normal)
{
if (_cache.TryGetValue(cacheKey, out T? cachedValue))
{
_logger.LogDebug("Retrieved data from cache with key: {CacheKey}", cacheKey);
return cachedValue!;
}
_logger.LogDebug("Cache miss for key: {CacheKey}, fetching fresh data", cacheKey);
var data = await dataProvider();
var cacheOptions = new MemoryCacheEntryOptions
{
SlidingExpiration = slidingExpiration,
AbsoluteExpirationRelativeToNow = absoluteExpiration,
Priority = priority
};
_cache.Set(cacheKey, data, cacheOptions);
_logger.LogDebug("Cached data with key: {CacheKey} with sliding expiration", cacheKey);
return data;
}
///
/// Removes data from cache
///
/// Cache key to remove
protected void RemoveFromCache(string cacheKey)
{
_cache.Remove(cacheKey);
_logger.LogDebug("Removed cache entry with key: {CacheKey}", cacheKey);
}
///
/// Gets configuration value with fallback
///
/// Type of configuration value
/// Configuration key
/// Default value if key not found
/// Configuration value or default
protected T GetConfigurationValue(string key, T defaultValue)
{
return _configuration.GetValue(key) ?? defaultValue;
}
///
/// Validates arguments for null values
///
/// Dictionary of argument names and values
/// Thrown if any argument is null
protected static void ValidateArguments(Dictionary arguments)
{
foreach (var arg in arguments)
{
if (arg.Value == null)
{
throw new ArgumentNullException(arg.Key);
}
}
}
///
/// Creates a logging scope with operation context
///
/// Name of the operation
/// Additional context data
/// Disposable logging scope
protected IDisposable? CreateLogScope(string operationName, Dictionary? additionalContext = null)
{
var scopeData = new Dictionary
{
["Operation"] = operationName,
["Service"] = GetType().Name
};
if (additionalContext != null)
{
foreach (var item in additionalContext)
{
scopeData[item.Key] = item.Value;
}
}
return _logger.BeginScope(scopeData);
}
}