using MP.Data; using Newtonsoft.Json; using NLog; using StackExchange.Redis; using System.Collections.Concurrent; using System.Diagnostics; using System.Runtime.CompilerServices; namespace MP.IOC.Services { /// /// Classe base per i servizi che fornisce funzionalità comuni come /// - connessione a Redis /// - configurazione /// - gestione dei messaggi /// - strategie di caching. /// /// Questa classe agisce come modello per altri servizi derivati. /// public class BaseServ { #region Public Constructors /// /// Inizializza una nuova istanza della classe BaseServ. /// Configura la connessione Redis, carica le impostazioni di configurazione e inizializza il serializzatore JSON. /// /// Oggetto di configurazione per recuperare le impostazioni dell'applicazione. /// Multiplexer di connessione Redis per operazioni sul database. public BaseServ(IConfiguration Configuration, IConnectionMultiplexer RedisConn) { _config = Configuration; _redisConn = RedisConn; _redisDb = _redisConn.GetDatabase(); // configuro la base key x la cache Redis, con verifica contenga Cache finale _redisBaseKey = _config.GetValue("ServerConf:RedisBaseKey") ?? "Lux:Cache"; // aggiungo cache se non finisse per ":cache" if (!_redisBaseKey.EndsWith(":Cache")) { _redisBaseKey += ":Cache"; } // Configurazione serializzatore JSON per risolvere errore di loop circolare JSSettings = new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; } #endregion Public Constructors #region Public Properties /// /// Pipe dei messaggi per la comunicazione riguardo update calcolo BOM /// public MessagePipe PipeBom { get; set; } = null!; /// /// Pipe dei messaggi per ritorno HwList da Engine di calcolo verso interfaccia utente. /// I messaggi vengono inviati sul canale Redis definito da ChannelHwList. /// public MessagePipe PipeHwList { get; set; } = null!; /// /// Pipe dei messaggi per ritorno HwOptions calcolate da Engine di calcolo verso interfaccia utente. /// I messaggi vengono inviati sul canale Redis definito da ChannelHwOpt. /// public MessagePipe PipeHwOpt { get; set; } = null!; /// /// Pipe dei messaggi per ritorno PNG calcolati da Engine di calcolo verso interfaccia utente. /// I messaggi vengono inviati sul canale Redis definito da ChannelPng. /// public MessagePipe PipePng { get; set; } = null!; /// /// Canale informazioni relativo ad attività relative alla gesitone PROD: /// - carico macchine /// - scheduling /// public MessagePipe PipeProd { get; set; } = null!; /// /// Pipe dei messaggi per ritorno info elementi del profile /// public MessagePipe PipeProfElement { get; set; } = null!; /// /// Pipe dei messaggi per ritorno ProfileListAsync calcolate da Engine di calcolo verso interfaccia utente. /// I messaggi vengono inviati sul canale Redis definito da ChannelProfList. /// public MessagePipe PipeProfList { get; set; } = null!; /// /// Pipe dei messaggi per ritorno Shape calcolate da Engine di calcolo verso interfaccia utente. /// I messaggi vengono inviati sul canale Redis definito da ChannelShape. /// public MessagePipe PipeShape { get; set; } = null!; /// /// Pipe dei messaggi per ritorno SVG calcolati da Engine di calcolo verso interfaccia utente. /// I messaggi vengono inviati sul canale Redis definito da ChannelSvg. /// public MessagePipe PipeSvg { get; set; } = null!; /// /// Pipe dei messaggi per la comunicazione riguardo update generico UI /// public MessagePipe PipeUpdate { get; set; } = null!; #endregion Public Properties #region Protected Fields /// /// Oggetto per collezione dati Activity (span in Uptrace) /// protected static readonly ActivitySource ActivitySource = new ActivitySource("Lux.DATA"); /// /// Oggetto logger utilizzato per registrare eventi e errori a livello di classe. /// Utile per il monitoraggio del comportamento dell'applicazione e la risoluzione di problemi. /// protected static Logger Log = LogManager.GetCurrentClassLogger(); /// /// Oggetto di configurazione statico per accedere alle impostazioni dell'applicazione (es. stringhe di connessione). /// Condiviso tra tutte le istanze di BaseServ. /// protected readonly IConfiguration _config = null!; /// /// Path base chiavi REDIS /// protected readonly string _redisBaseKey = "Lux:Cache"; /// /// Oggetto per la connessione a Redis utilizzato per operazioni di lettura/scrittura. /// protected readonly IConnectionMultiplexer _redisConn = null!; /// /// Database Redis utilizzato per le operazioni di lettura/scrittura /// nb: ottenuto tramite _redisConn.GetDatabase() /// protected readonly IDatabase _redisDb = null!; /// /// Abilitazione operazioni tracing generiche /// protected readonly bool _traceEnabled = false; /// /// Impostazioni del serializzatore JSON utilizzato per gestire oggetti con riferimenti circolari /// (es. oggetti che si fanno riferimento reciprocamente). /// protected JsonSerializerSettings? JSSettings; #endregion Protected Fields #region Protected Properties /// /// Durata della cache breve (circa 1 minuto + variazione del +/-10%) /// protected TimeSpan FastCache { get => TimeSpan.FromSeconds(cacheTtlShort * rnd.Next(900, 1100) / 1000); } /// /// Durata della cache lunga (+ variazione del +/-10%) /// protected TimeSpan LongCache { get => TimeSpan.FromSeconds(cacheTtlLong * rnd.Next(900, 1100) / 1000); } /// /// Durata della cache molto breve (circa 10 secondi + variazione del +/-10%) /// protected TimeSpan UltraFastCache { get => TimeSpan.FromSeconds(cacheTtlShort / 6 * rnd.Next(900, 1100) / 1000); } /// /// Durata della cache molto lunga (+ variazione del +/-10%) /// protected TimeSpan UltraLongCache { get => TimeSpan.FromSeconds(cacheTtlLong * 10 * rnd.Next(900, 1100) / 1000); } #endregion Protected Properties #region Protected Methods /// /// Helper avvio attività per la funzione tracciata /// /// /// protected static Activity? StartActivity([CallerMemberName] string? methodName = null) { var activity = ActivitySource.StartActivity(methodName ?? "UNDEF"); activity?.SetTag("host.name", Environment.MachineName); return activity; } /// /// Invalida una o più chiavi/pattern in Redis /// protected async Task ClearCacheAsync(params string[] patterns) { foreach (var pattern in patterns) { // Chiamata al tuo metodo esistente await ExecFlushRedisPatternAsync((RedisValue)pattern); } } /// /// Metodo di flush dati cache Redis /// /// /// protected async Task ExecFlushRedisPatternAsync(RedisValue pattern) { // Qui inserisci la tua logica attuale (es. via Lua script o Keys/Scan) // Esempio rapido via server scan: var endpoints = _redisConn.GetEndPoints(); foreach (var endpoint in endpoints) { var server = _redisConn.GetServer(endpoint); await foreach (var key in server.KeysAsync(_redisDb.Database, pattern)) { await _redisDb.KeyDeleteAsync(key); } } } /// /// Helper generale di lettura da cache o da funzione (DB) con caching successivo /// /// /// /// /// /// protected async Task GetOrSetCacheAsync(string key, Func> factory, TimeSpan? expiration = null, [CallerMemberName] string? caller = null) { using var activity = StartActivity(); string source = "DB"; // 1. Provo Redis var cached = await _redisDb.StringGetAsync(key); if (cached.HasValue) { source = "REDIS"; var cachedResult = JsonConvert.DeserializeObject(cached!)!; activity?.SetTag("data.source", source); LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms", NLog.LogLevel.Trace, caller); return cachedResult; } // 2. Chiamo il factory (DB) T result = await factory(); if (result != null) { // 3. Salva in Redis per la prossima volta var serialized = JsonConvert.SerializeObject(result, JSSettings); await _redisDb.StringSetAsync(key, serialized, expiration ?? LongCache); } // sistemo activity tracking data activity?.SetTag("data.source", source); activity?.Stop(); // log in console LogTrace($"GetOrSetCacheAsync | {source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds:N3}ms", NLog.LogLevel.Trace, caller); return result!; } /// /// Helper trace messaggio log (SE abilitato) /// /// /// /// protected void LogTrace(string traceMsg, NLog.LogLevel? reqLevel = null, [CallerMemberName] string? methodName = null) { if (!_traceEnabled) return; reqLevel ??= NLog.LogLevel.Debug; // Loggo! Log.Log(reqLevel, $"{methodName} | {traceMsg}"); } /// /// Helper generale per la telemetria e gestione eccezioni /// /// /// /// /// /// protected async Task TraceAsync(string name, Func> body, object? parameters = null) { using var activity = ActivitySource.StartActivity(name); try { if (parameters != null) { activity?.SetTag("params", JsonConvert.SerializeObject(parameters)); } var result = await body(activity); activity?.SetStatus(ActivityStatusCode.Ok); activity?.Stop(); LogTrace($"TraceAsync | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms", methodName: name); return result; } catch (Exception ex) { activity?.SetStatus(ActivityStatusCode.Error, ex.Message); //Log.Error(ex, "Errore in {MethodName}", name); LogTrace($"Errore in {name}", NLog.LogLevel.Error, name); throw; // Riesponi l'eccezione per il tracking globale } } #endregion Protected Methods #region Private Fields private static readonly ConcurrentDictionary _locks = new(); /// /// Durata della cache lunga in secondi (predefinito: 5 minuti) /// Utilizzato nella proprietà LongCache per definire quanto a lungo i dati devono essere memorizzati in cache. /// private int cacheTtlLong = 60 * 5; /// /// Durata della cache breve in secondi (predefinito: 1 minuto) /// Utilizzato nelle proprietà FastCache e UltraFastCache per definire la durata della cache breve. /// private int cacheTtlShort = 60 * 1; /// /// Generatore di numeri casuali utilizzato per introdurre variabilità dinamica nelle durate della cache /// (simula variazioni reali nella freschezza dei dati e nei tempi di scadenza). /// private Random rnd = new Random(); #endregion Private Fields #region Private Methods #endregion Private Methods } }