# Piano Migrazione MpDataService → IIocService > **Obiettivo**: Standardizzare l'accesso dati su `IIocService` (con FusionCache) e pensionare `MpDataService` come service layer principale di `MP.IOC`. > > **Strategia**: Ogni metodo `MpDataService` usato dal controller viene migrato in `IocService`. La cache FusionCache avvolge le chiamate Redis/DB con le stesse durate e tag esistenti. I controller usano solo `IIocService`. --- ## Architettura Target ``` IOBController └── IIocService (scoped) ├── FusionCache (L1 Memory + L2 Redis + L3 DB via tags) ├── StackExchange.Redis (IDatabase) ← sorgente "live" per dati esterni └── IIocRepository (scoped) ← DB access ``` **Regola chiave**: Redis diretto (scritto da IOB-WIN, altri programmi) resta accessibile via `IDatabase _redisDb` in `IocService`. FusionCache ha una durata equivalente per validare/invalidare coerentemente. --- ## Stato Attuale ### Minimal APIs — OK I 3 endpoint in `IobEndpoints.cs` sono stati puliti e ora contengono solo: - `GET api/IOB` / `api/IOB/alive` — health check - `GET api/IOB/enabled/{id}` — usa `await iocService.IobInsEnabAsync(id)` I metodi `setCounter` e `getCurrODL` sono stati rimossi (troppo semplici, nel controller bastano). ### Controller — Misto | Usa DService | Usa IOCService | |---|---| | ~37 metodi | 5 metodi (`getCounterTCRec`, `getCurrODL`, `setCounter`, `input`, `getTask2Exe` in parte) | ### MpDataService - Registrato come **singleton** in `Program.cs:106` - Contiene ~80 metodi, molti con accesso Redis diretto senza FusionCache - `IocService` è **scoped** e usa già FusionCache ### IocService - Eredita da `MP.Data.Services.BaseServ` (contiene `GetOrFetchAsync`) - 12 metodi pubblici nell'interface - Usa FusionCache per i metodi più critici (`IobInsEnabAsync`, `GetCurrOdlAsync`, `StatoProdMacchinaAsync`) --- ## Piano di Migrazione — Fasi ### Fase 0: Preparazione (`IocService` base) **File**: `IIocService.cs`, `IocService.cs` 0.1. `IocService` deve ereditare da `MP.Data.Services.BaseServ` (attuale `IocService` non lo fa) — per accedere a `GetOrFetchAsync` condiviso 0.2. Aggiunta di `IFusionCache _cache` nel constructor (già presente in `BaseServ`) 0.3. Verifica: `redisLongTimeCache` e `redisShortTimeCache` usati da `IocService` per le durate FusionCache --- ### Fase 1: Metodi KeepAlive + Task Management **Priorità**: Alta — usati da quasi tutti gli altri metodi come precursori. | MpDataService | → IocService | NOTE | |---|---|---| | `ScriviKeepAliveAsync` | ✓ già duplicato in `IocService` | Unificare, aggiungere FusionCache per la check key | | `AddOptPar4Machine` | → `AddOptPar4Machine` | Redis hash, cache per tag machine | | `AddTask4Machine` | → `AddTask4Machine` | Redis hash + backup, cache per tag machine | | `mOptParMacchina` | → `GetOptParMacchina` | Redis hash read | | `mTaskMacchina` / `mTaskMacchinaAsync` | → `GetTaskMacchina` | Redis hash read | | `RemTask2ExeMacchinaAsync` | → `RemoveTask2ExeAsync` | Redis hash write + delete | | `mSavedTaskMacchina` | → `GetSavedTasksMacchina` | Redis hash read | | `AddCheckTask4Machine` | → `AddCheckTask4Machine` | Redis hash read/write | | `AddTask4MacListAsync` | → `AddTaskListAsync` | Redis hash batch write | **Durata cache**: `redisShortTimeCache` (2 min + jitter) per task hash (dati volatile) **Tag cache**: `Task:{idxMacchina}`, `OptPar:{idxMacchina}`, `SavedTask:{idxMacchina}` --- ### Fase 2: Metodi Process (INPUT, FLOG, ULOG) **Priorità**: Altissima — il cuore del sistema, alto traffico. | MpDataService | → IocService | NOTE | |---|---|---| | `ProcessInputAsync` | → `ProcessInputAsync` | GIÀ in `IocService` ✓ | | `ProcessFluxLogAsync` | → `ProcessFluxLogAsync` | DB insert + Redis write | | `ProcessUserLogAsync` | → `ProcessUserLogAsync` | DB insert multi-flux | | `CheckMicroStatoAsync` | → `CheckMicroStatoAsync` | DB upsert + transizione | | `scriviRigaEventoAsync` | → `WriteEventRecordAsync` | DB insert | | `saveSigLogAsync` | → `SaveSignalLogAsync` | DB insert | | `GetSrvDtEvent` | → `GetServerDateTime` | Pure logic — no cache | | `ParseEventTime` | → `ParseEventTime` | GIÀ in `IocService` ✓ | | `preProcInput` | → `PreProcessInput` | Pure logic — no cache | | `ValidateInputParams` / `ValidateinputParams` | → `ValidateInputParams` | Pure logic — no cache | **Durata cache**: Nessuna (scrittura). Tag invalidazione per `MachineData:{idxMacchina}` sui dati che influenzano. **Tag cache**: `Input:{idxMacchina}`, `FluxLog:{idxMacchina}`, `UserLog:{idxMacchina}`, `MicroStato:{idxMacchina}` --- ### Fase 3: Metodi ODL/PODL **Priorità**: Alta — letture frequenti durante produzione. | MpDataService | → IocService | NOTE | |---|---|---| | `GetCurrOdlAsync` | → `GetCurrOdlAsync` | GIÀ in `IocService` ✓ | | `GetLastOdlAsync` | → `GetLastOdlAsync` | Usare `GetOrFetchAsync` | | `OdlCurrByMaccAsync` | → `GetCurrentOdlAsync` | Usare `GetOrFetchAsync` | | `AutoStartOdlAsync` | → `AutoStartOdlAsync` | Complesso — Redis + DB + eventi | | `FixDailyDossierAsync` | → `FixDailyDossierAsync` | Chiamata batch | | `OdlAutoDayGenAsync` | → `GenerateDailyOdlAsync` | DB stored procedure | | `OdlAutoDayGenFullAsync` | → `GenerateDailyOdlFullAsync` | DB stored procedure | | `POdlGetByKey` | → `GetPodlByKeyAsync` | Usare `GetOrFetchAsync` | | `POdlGetByMaccArtAsync` | → `GetPodlByMachineAsync` | Usare `GetOrFetchAsync` | **Durata cache**: `redisLongTimeCache` (5 min) per ODL/PODL **Tag cache**: `Odl:{idxMacchina}`, `Podl:{idxMacchina}` --- ### Fase 4: Metodi Contapezzi **Priorità**: Alta — traffico continuo. | MpDataService | → IocService | NOTE | |---|---|---| | `pzCounter` | → `GetPzCounterAsync` | Redis StringGet (lettura live) | | `PzCounterTcAsync` | → `PzCounterTcAsync` | GIÀ in `IocService` ✓ | | `saveCaricoPezzi` | → `SavePezziCaricoAsync` | Redis StringSet + DB insert | | `SaveCounterAsync` | → `SaveCounterAsync` | GIÀ in `IocService` ✓ | **Durata cache**: `redisShortTimeCache` per counter (dati volatile scritti da IOB esterni) **Tag cache**: `PzCounter:{idxMacchina}` --- ### Fase 5: Metodi Parametri Macchina **Priorità**: Alta — metodo più chiamato (ogni richiesta legge i parametri). | MpDataService | → IocService | NOTE | |---|---|---| | `MachineParamListAsync` | → `GetMachineParamsAsync` | **CRITICO** — Redis read, aggiungere FusionCache | | `MachineParamListPendingWriteAsync` | → `GetPendingWriteParamsAsync` | Wrapper su GetMachineParamsAsync | | `MachineParamListSetAsync` | → `SetMachineParamsAsync` | Redis StringSet | | `MachineParamUpsertAsync` | → `UpsertMachineParamsAsync` | Redis read + write batch | | `UpsertCurrObjItemsAsync` | → `UpsertMachineParamsAsync` | Duplicate — unificare con UpsertMachineParamsAsync | **Durata cache**: `redisShortTimeCache` (2 min) per parametri macchina — dati scritti da IOB esterni ogni pochi secondi **Tag cache**: `MachineParams:{idxMacchina}` --- ### Fase 6: Metodi Anagrafica e Lookup **Priorità**: Media — dati relativamente statici. | MpDataService | → IocService | NOTE | |---|---|---| | `AnagStatiGetAllAsync` | → `GetAllStatiAsync` | GIÀ usa `GetOrFetchAsync` in MpDataService | | `ArticoliGetLastByMaccAsync` | → `GetLastArticlesByMachineAsync` | GIÀ usa `GetOrFetchAsync` in MpDataService | | `DossierLastByMachAsync` | → `GetLastDossiersByMachineAsync` | GIÀ usa `GetOrFetchAsync` in MpDataService | | `ListValuesFilt` | → `GetListValuesFilteredAsync` | Redis read (duplicare logica FusionCache) | | `DecNumArtGetFiltAsync` | → `GetDecNumArticoliAsync` | GIÀ usa `GetOrFetchAsync` in MpDataService | | `MacchineGetFilt` | → `GetMachinesFilteredAsync` | GIÀ usa `GetOrFetchAsync` in MpDataService | | `Macchine2SlaveGetAllAsync` | → `GetMachineSlaveMapAsync` | GIÀ usa `GetOrFetchAsync` in MpDataService | | `MacchineRecipeArchive` | → `GetMachineRecipeArchivePathAsync` | GIÀ usa `GetOrFetchAsync` in MpDataService | | `ConfigGetAllAsync` | → `GetAllConfigAsync` | GIÀ usa `GetOrFetchAsync` in MpDataService | | `ConfFluxMach` | → `GetConfFluxByMachineAsync` | GIÀ usa `GetOrFetchAsync` in MpDataService | | `GetArtNumAsync` | → `GetArtNumByCodeAsync` | Interno — wrapper a DecNumArtGetFiltAsync | | `MseGetAllAsync` | → `GetMachineStateExplAsync` | GIÀ usa `GetOrFetchAsync` in MpDataService | **Durata cache**: `redisLongTimeCache` (5 min + jitter) **Tag cache**: `AnagStati`, `Articoli:{idxMacchina}`, `Dossier:{idxMacchina}`, `Config`, `MSE`, `Macchine` --- ### Fase 7: Metodi Configurazione IOB + Redis Diretto **Priorità**: Bassa — configurazione manuale, basso traffico. | MpDataService | → IocService | NOTE | |---|---|---| | `SetIobMemMap` | → `SetIobMemMapAsync` | Redis StringSet | | `SetIobConfYamlAsync` | → `SetIobConfYamlAsync` | Redis StringSet | | `SaveMachineIobConf` | → `SaveMachineIobConfAsync` | Redis HashSet | | `SaveMachine2Iob` | → `SaveMachine2IobAsync` | Redis StringSet | | `SaveDataItemsAsync` | → `SaveDataItemsAsync` | Mongo o DB | | `GetCurrObjItems` | → `GetMachineParamsAsync` | **Duplicate** — usare Fase 5 | --- ### Fase 8: Metodi Logging e Monitoring **Priorità**: Bassa — writing-only, nessun cache. | MpDataService | → IocService | NOTE | |---|---|---| | `AlarmInsertAsync` | → `InsertAlarmLogAsync` | DB insert | | `RemRebootLogAddAsync` | → `AddRebootLogAsync` | DB insert + Redis flush | | `WriteEventRecordAsync` (scriviRigaEventoAsync) | → `WriteEventRecordAsync` | DB insert | | `FluxLogSaveSnapshotAsync` | → `SaveFluxSnapshotAsync` | DB stored procedure | | `DossierLastByMachResetAsync` | → `ResetDossierCacheAsync` | Redis delete | | `FluxLogFirstByMachAsync` | → `GetFirstFluxLogsAsync` | GIÀ usa `GetOrFetchAsync` in MpDataService | --- ### Fase 9: Metodi Helper e Internal **Priorità**: Bassa — usati internamente. | MpDataService | → IocService | NOTE | |---|---|---| | `mDatiMacchineAsync` | → `GetMachineDataAsync` | Redis HashGetAll + reset | | `ResetDatiMacchinaAsync` | → `ResetMachineDataAsync` | Redis transaction batch | | `StateMachInByKeyAsync` | → `GetStateMachineIngressiAsync` | Redis Hash | | `resetMSMIAsync` | → `ResetMultiSMIAsync` | Redis Hash write | | `resetSMIAsync` | → `ResetSMIAsync` | Redis Hash write | | `ValoreSmiAsync` | → `GetSMIValueAsync` | Redis HashField get | | `isMulti` | → `IsMultiMachine` | Pure logic | | `ListMasterAsync` / `ListSlaveAsync` | → `GetMasterListAsync` / `GetSlaveListAsync` | Redis cache | | `getRandTOut` | → `GetRandomTimeout` | Shared in BaseServ | | `RedisCountKey`, `RedisDelKey`, `RedisFlushPatternAsync` | → `FlushKeysByPatternAsync` | Admin only | | `RedisGetHashDictAsync`, `RedisSetHashDictAsync` | → `RedisGet/HashDictAsync` | Wrapper utilities | --- ## Fase 10: Pulizia Controller ### 10.1. Sostituire `DService` con `IOCService` in `IOBController` Cambiare constructor e tutte le chiamate: ```csharp // PRIMA public IOBController(IConfiguration configuration, MpDataService DataService, IIocService IService) { DService = DataService; IOCService = IService; } // DOPO public IOBController(IIocService service) { _service = service; } ``` Poi sostituire ogni `DService.Xxx(...)` con `_service.Xxx(...)`. ### 10.2. Rimuovere `MpDataService` dal controller - Rimuovere la property `DService` - Rimuovere il parametro dal constructor - Unificare le chiamate miste (alcuni metodi fanno `DService.ScriviKeepAliveAsync` + `IOCService.Xxx`) ### 10.3. Spostare i private method in `IocService` I metodi `processEvListJsonAsync`, `processFLogJsonAsync`, `processULogJsonAsync` contengono logica di business: - `processEvListJsonAsync` → `ProcessEventListJsonAsync` in `IocService` - `processFLogJsonAsync` → `ProcessFluxLogJsonAsync` in `IocService` (complesso — usa `MachineParamListAsync` + `UpsertCurrObjItemsAsync`) - `processULogJsonAsync` → `ProcessUserLogJsonAsync` in `IocService` ### 10.4. Rimuovere `MpDataService` da `Program.cs` ```csharp // RIMUOVERE: // builder.Services.AddSingleton(); ``` --- ## Riepilogo Metodi da Migrire | Categoria | Metodi da aggiungere a IocService | |---|---| | KeepAlive + Task | ~9 metodi | | Process (INPUT/FLOG/ULOG) | ~9 metodi | | ODL/PODL | ~8 metodi | | Contapezzi | ~3 metodi | | Parametri Macchina | ~4 metodi | | Anagrafica/Lookup | ~11 metodi | | Config IOB | ~5 metodi | | Logging | ~6 metodi | | Helper/Internal | ~10 metodi | | **TOTALE** | **~65 metodi** | Di questi, ~15 sono già in `IocService` o `BaseServ`. Quindi **~50 nuovi metodi** da implementare. --- ## Criteri di Validazione 1. **Build** — `./build_all_par.ps1 --agent` senza errori 2. **Functional parity** — ogni endpoint del controller restituisce lo stesso output di prima 3. **Cache behavior** — i metodi migrati registrano `data.source` come TRACE/DEBUG 4. **No `MpDataService` references** nel controller 5. **`Program.cs`** — `MpDataService` rimosso dalla DI registration --- ## Sequenza di Sviluppo Consigliata ``` Fase 1 → Fase 4: Blocchi "hot" (keepalive, task, counter) Fase 5: Parametri macchina (blocco più critico per perf) Fase 2: Process (INPUT/FLOG/ULOG - cuore dell'app) Fase 3: ODL/PODL Fase 6: Anagrafica (dati statici, meno rischioso) Fase 7-9: Config + Logging + Helper Fase 10: Cleanup finale ``` Ogni fase va build-ata e testata prima di procedere alla successiva. --- ## Note Tecniche ### Caching su Redis diretto Per i dati scritti da programmi esterni (IOB-WIN, ecc.), il pattern è: ``` 1. Lettura: _redisDb.StringGet/HashGet (dati "live") 2. Scrittura: programmi esterni scrivono in Redis direttamente 3. FusionCache: durata pari a redisShortTimeCache o redisLongTimeCache - Il backplane Redis invalida L1 negli altri worker - Se un programma esterno scrive, la prossima lettura dal controller fa cache-miss ``` Questo è coerente con l'architettura attuale: il controller legge Redis (sorgente di verità) e FusionCache avvolge per ridurre i round-trip. ### Tagging Strategy ``` MachineData:{idxMacchina} — dati macchina generali (ResetDatiMacchina) MachineParams:{idxMacchina} — parametri macchina (MachineParamListAsync) Task:{idxMacchina} — task da eseguire PzCounter:{idxMacchina} — contapezzi Odl:{idxMacchina} — ODL correnti Podl:{idxMacchina} — PODL Input:{idxMacchina} — log input FluxLog:{idxMacchina} — log flux UserLog:{idxMacchina} — log user ``` ### Transizioni di Scope `MpDataService` è singleton → `IIocService` è scoped. Questo va bene per le minimal APIs (che iniettano `IIocService` correttamente). Per il controller, dopo la rimozione di `MpDataService`, il controller diventerà scoped di default (già lo è per `[ApiController]`). ### IocService constructor Attuale (da `MP.Data.Services.IOC.IocService`): ```csharp public IocService(IConfiguration config, IConnectionMultiplexer redis, IFusionCache cache, IIocRepository repo, IServiceScopeFactory scopeFactory) ``` Da `MP.Data.Services.BaseServ`: ```csharp public BaseServ(IConfiguration configuration, IFusionCache cache, IConnectionMultiplexer redConn) ``` La gerarchia sarà: `IocService` estende `BaseServ` e il constructor chiama `base(configuration, cache, redConn)`.