205 lines
7.2 KiB
C#
205 lines
7.2 KiB
C#
using Microsoft.Extensions.Caching.Memory;
|
|
|
|
namespace BankSampahApp.Services;
|
|
|
|
/// <summary>
|
|
/// Base service class yang menyediakan functionality umum untuk semua services
|
|
/// Menerapkan prinsip DRY dengan mengekstrak common patterns
|
|
/// </summary>
|
|
public abstract class BaseService
|
|
{
|
|
protected readonly ILogger _logger;
|
|
protected readonly IMemoryCache _cache;
|
|
protected readonly IConfiguration _configuration;
|
|
|
|
/// <summary>
|
|
/// Constructor untuk base service
|
|
/// </summary>
|
|
/// <param name="logger">Logger instance</param>
|
|
/// <param name="cache">Memory cache instance</param>
|
|
/// <param name="configuration">Configuration instance</param>
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a function with comprehensive error handling and logging
|
|
/// </summary>
|
|
/// <typeparam name="T">Return type</typeparam>
|
|
/// <param name="operation">Function to execute</param>
|
|
/// <param name="operationName">Name of the operation for logging</param>
|
|
/// <param name="fallbackValue">Fallback value if operation fails</param>
|
|
/// <returns>Result of operation or fallback value</returns>
|
|
protected async Task<T> ExecuteWithErrorHandlingAsync<T>(
|
|
Func<Task<T>> 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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets cached data with configurable expiration
|
|
/// </summary>
|
|
/// <typeparam name="T">Type of cached data</typeparam>
|
|
/// <param name="cacheKey">Cache key</param>
|
|
/// <param name="dataProvider">Function to get data if not cached</param>
|
|
/// <param name="expiration">Cache expiration time</param>
|
|
/// <param name="priority">Cache priority</param>
|
|
/// <returns>Cached or fresh data</returns>
|
|
protected async Task<T> GetOrSetCacheAsync<T>(
|
|
string cacheKey,
|
|
Func<Task<T>> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets cached data with sliding expiration
|
|
/// </summary>
|
|
/// <typeparam name="T">Type of cached data</typeparam>
|
|
/// <param name="cacheKey">Cache key</param>
|
|
/// <param name="dataProvider">Function to get data if not cached</param>
|
|
/// <param name="slidingExpiration">Sliding expiration time</param>
|
|
/// <param name="absoluteExpiration">Absolute expiration time</param>
|
|
/// <param name="priority">Cache priority</param>
|
|
/// <returns>Cached or fresh data</returns>
|
|
protected async Task<T> GetOrSetCacheWithSlidingAsync<T>(
|
|
string cacheKey,
|
|
Func<Task<T>> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes data from cache
|
|
/// </summary>
|
|
/// <param name="cacheKey">Cache key to remove</param>
|
|
protected void RemoveFromCache(string cacheKey)
|
|
{
|
|
_cache.Remove(cacheKey);
|
|
_logger.LogDebug("Removed cache entry with key: {CacheKey}", cacheKey);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets configuration value with fallback
|
|
/// </summary>
|
|
/// <typeparam name="T">Type of configuration value</typeparam>
|
|
/// <param name="key">Configuration key</param>
|
|
/// <param name="defaultValue">Default value if key not found</param>
|
|
/// <returns>Configuration value or default</returns>
|
|
protected T GetConfigurationValue<T>(string key, T defaultValue)
|
|
{
|
|
return _configuration.GetValue<T>(key) ?? defaultValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates arguments for null values
|
|
/// </summary>
|
|
/// <param name="arguments">Dictionary of argument names and values</param>
|
|
/// <exception cref="ArgumentNullException">Thrown if any argument is null</exception>
|
|
protected static void ValidateArguments(Dictionary<string, object?> arguments)
|
|
{
|
|
foreach (var arg in arguments)
|
|
{
|
|
if (arg.Value == null)
|
|
{
|
|
throw new ArgumentNullException(arg.Key);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a logging scope with operation context
|
|
/// </summary>
|
|
/// <param name="operationName">Name of the operation</param>
|
|
/// <param name="additionalContext">Additional context data</param>
|
|
/// <returns>Disposable logging scope</returns>
|
|
protected IDisposable? CreateLogScope(string operationName, Dictionary<string, object>? additionalContext = null)
|
|
{
|
|
var scopeData = new Dictionary<string, object>
|
|
{
|
|
["Operation"] = operationName,
|
|
["Service"] = GetType().Name
|
|
};
|
|
|
|
if (additionalContext != null)
|
|
{
|
|
foreach (var item in additionalContext)
|
|
{
|
|
scopeData[item.Key] = item.Value;
|
|
}
|
|
}
|
|
|
|
return _logger.BeginScope(scopeData);
|
|
}
|
|
} |