using Microsoft.Extensions.Caching.Memory; namespace BpsRwApp.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); } }