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);
 | |
|     }
 | |
| } |