# Piano di Refactoring: MP.IOC ## Obiettivo Modernizzare il progetto MP.IOC allineandolo agli standard architettonici definiti in MP.SPEC: 1. Rimuovere l'uso di `MpIocController` come servizio di accesso dati diretto 2. Migrare la logica di `MpDataService` ai repository esistenti (`IIocRepository`, `IStatsAggrRepository`, etc.) 3. Standardizzare il caching manuale Redis → FusionCache `GetOrFetchAsync` 4. Correggere tutti gli anti-pattern statici e DI ## Stato Attuale | File | Riga | Tipo | Descrizione | |------|------|------|-------------| | `IOBController.cs` | 1675 | ✅ Controller ASP.NET | 52 endpoints REST (api/IOB) | | `BenchController.cs` | 387 | ✅ Controller ASP.NET | Test/bench Redis | | `RecipeController.cs` | 73 | ✅ Controller ASP.NET | API ricette (2 metodi) | | `RecipeArchiveController.cs` | 130 | ✅ Controller ASP.NET | API ricette file (2 metodi) | | `MpDataService.cs` | 3516 | ❌ Data Hub | Singleton, ~100 metodi, bypassa repository | | `MpIocController.cs` | 1480 | ⚠️ Wrong location | 82 metodi EFCore in MP.Data, usato come servizio | ### Struttura DI Attuale (Post-Refactoring) ``` Program.cs ├── AddIocDataLayer() da MP.Data │ ├── MpIocController ── Singleton ← usato direttamente da MpDataService │ ├── IIocRepository ── Scoped ← mai usato da MpDataService │ ├── IStatsAggrRepository ── Scoped ← mai usato │ └── IStatsDetailRepository ── Scoped ← mai usato ├── MpDataService ── Singleton ← chiama IocDbController (statico), usa IFusionCache └── IFusionCache ── L1 Memory + L2 Redis Distributed + Redis Backplane ✅ ``` ### Problemi Chiave 1. **MpIocController come servizio** — 82 metodi EFCore classificati ingannevolmente come "Controller" ma in realtà sono un servizio di accesso dati, registrato come Singleton con DbContext che non è thread-safe 2. **MpDataService bypassa repository** — ~80% dei metodi chiama `IocDbController.XXX()` direttamente invece di usare `IIocRepository` 3. **Caching misto** — Parte migrato a FusionCache, parte ancora in manual StringSet/StringGet 4. **Singleton lifetime mismatch** — MpDataService è Singleton, registra campi statici per IoC Controller e Mongo Controller che vengono sovrascritti ## Piano di Refactoring ### Fase 1: Pulizia Anti-Pattern (Quick Wins) ✅ COMPLETATA Rimosso `static IConfiguration _configuration` da tutti i 4 controller e rimossa la dead assignment dai costruttori: | Controller | Prima | Dopo | |------------|-------|------| | `IOBController` | `IConfiguration + static field` | Nessun param (dead code), field rimosso | | `BenchController` | `IConfiguration + static field` | Solo `MpDataService`, field rimosso | | `RecipeController` | `IConfiguration + static field` | Solo `MpDataService`, field rimosso | | `RecipeArchiveController` | `IConfiguration + static field` | Solo `MpDataService`, field rimosso | ### Fase 2: FusionCache + Redis Backplane ✅ COMPLETATA #### 2.1 Pacchetti Aggiunti (MP.IOC.csproj) | Pacchetto | Stato | |-----------|-------| | `Microsoft.Extensions.Caching.StackExchangeRedis` | ✅ | | `ZiggyCreatures.FusionCache` | ✅ | | `ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis` | ✅ | | `ZiggyCreatures.FusionCache.Serialization.NewtonsoftJson` | ✅ | #### 2.2 Configurazione FusionCache (Program.cs) ```csharp // L1 Memory + L2 Redis Distributed + Redis Backplane builder.Services.AddStackExchangeRedisCache(options => options.Configuration = confRedis); builder.Services.AddFusionCache() .WithDistributedCache(sp => sp.GetRequiredService()) .WithSerializer(new FusionCacheNewtonsoftJsonSerializer()) .WithBackplane(new RedisBackplane(new RedisBackplaneOptions { ConnectionMultiplexerFactory = () => Task.FromResult(redisMux) })) .WithDefaultEntryOptions(options => { options.Duration = TimeSpan.FromMinutes(1); options.JitterMaxDuration = TimeSpan.FromSeconds(5); }); ``` #### 2.3 Migrate 14 metodi da manual Redis a FusionCache | # | Metodo | Categoria | Fetch From | |---|--------|-----------|------------| | 1 | `AnagStatiGetAllAsync` | Anagrafica | `IocDbController.AnagStatiGetAllAsync()` | | 2 | `ArticoliGetLastByMaccAsync` | Anagrafica | `IocDbController.ArticoliGetLastByMaccAsync()` | | 3 | `ConfigGetAllAsync` | Anagrafica | `IocDbController.ConfigGetAllAsync()` | | 4 | `DecNumArtGetFiltAsync` | Anagrafica | `IocDbController.DecNumArtGetFiltAsync()` | | 5 | `DossierLastByMachAsync` | Dossier | `IocDbController.DossGetLastByMaccAsync()` | | 6 | `GetLastOdlAsync` | Produzione | `IocDbController.OdlLastByMaccAsync()` | | 7 | `ListValuesFilt` | Configurazione | `IocDbController.ListValuesFiltAsync()` | | 8 | `Macchine2SlaveGetAllAsync` | Machine | `IocDbController.Macchine2SlaveAsync()` | | 9 | `MacchineGetFilt` | Machine | `_productionRepository.MacchineGetFiltAsync()` | | 10 | `MacchineRecipeArchive` | Machine | `_productionRepository.MacchineGetFiltAsync("*")` | | 11 | `MseGetAllAsync` | Stato | `IocDbController.MseGetAllAsync(maxAge)` (+ forceDb) | | 12 | `OdlCurrByMaccAsync` | Produzione | `IocDbController.OdlCurrByMaccAsync()` | | 13 | `POdlGetByKey` | Produzione | `_productionRepository.PODL_getByKeyAsync()` | | 14 | `POdlGetByMaccArtAsync` | Produzione | `IocDbController.POdlGetByMaccArtAsync()` | | 15 | `ConfFluxMach` | FluxLog | `IocDbController.ConfFluxFiltAsync()` | | 16 | `FluxLogFirstByMachAsync` | FluxLog | `IocDbController.FluxLogFirstByMaccAsync()` | | 17 | `FluxLogGetLastByMachAsync` | FluxLog | `IocDbController.FluxLogGetLastFiltAsync()` | ### Fase 3: Decomposizione MpIocController in Repository (NON INIZIATA) `MpIocController` (1480 righe, 82 metodi) va scomposto in repository specialistici, come fatto per MP.SPEC: | Attuale (82 metodi) | Nuovo Repository | Ambito | |----------------------|-------------------|--------| | `AnagStatiGetAllAsync`, `ArticoliGetLastByMaccAsync`, `ConfigGetAllAsync`, `ConfigUpdateAsync`, `DecNumArtGetFiltAsync`, `ListValuesFiltAsync`, `ListLinkFiltAsync`, `EvListInsert` | `IAnagRepository` | **Usato da SPEC** — già migrato! | | `AutoStartOdlAsync`, `ConfirmaProdMacchinaAsync/Full`, `OdlCurrByMaccAsync`, `OdlLastByMaccAsync`, `OdlListByMaccPeriodoAsync`, `PezziProdMacchinaAsync`, `StatoProdMacchinaAsync` | `IProduzioneRepository` (nuovo) | Produzione IOB-specifica | | `OdlAutoDayGenAsync/Full`, `POdlGetByMaccArtAsync`, `OdlFixMachineSlave` | `IProduzioneRepository` (nuovo) | Pianificazione produzione | | `FluxLogInsertAsync`, `FluxLogGetLastFiltAsync`, `FluxLogFirstByMaccAsync`, `FluxLogTakeSnapshotLastAsync`, `ConfFluxFiltAsync` | `IFluxLogRepository` | **Usato da SPEC** — già migrato! | | `DossGetLastByMaccAsync`, `SignalLogInsertAsync` | `IDossierRepository` | **Usato da SPEC** — già migrato! | | `DatiMacchineGetAllAsync`, `MacchineGetByIdxAsync`, `MacchineUpsertAsync`, `MacchineGetFiltAsync`, `MacchineGetAllAsync`, `Macchine2Slave` | `IMacchineRepository` (nuovo) | Gestione macchine | | `EvListMicroStatoInsertAsync`, `MicroStatoMacchinaUpsertAsync`, `MicroStatoMacchinaGetByIdxMaccAsync` | `IMicroStatoRepository` (nuovo) | Micro-stati macchine | | `MSE_getUserForcedAsync`, `SMES_getHwTransitionsAsync`, `DDB_InsStatoBatchAsync`, `CheckCambiaStatoBatchAsync`, `RecalcMseAsync` | `IMseRepository` (nuovo) | Mappa stati macchin | | `StateMachineIngressiAsync`, `VMSFDGetByMaccAsync`, `VMSFDGetMultiByMaccAsync`, `VMSFDGetAllAsync` | `IStateMachineRepository` (nuovo) | State machine | | `KeepAliveUpsertAsync` | `IMacchineRepository` (nuovo) | Keep-alive | | `AlarmLogInsertAsync`, `RegScartiInsertAsync`, `RegControlliInsertAsync`, `RegDichiarInsertAsync` | `IRegistroRepository` (nuovo) | Registri qualità | | `RemRebootLog*` methods (6) | `IRemRebootRepository` (nuovo) | Reboot remote logging | ### Fase 4: Scomposizione MpDataService (NON INIZIATA) `MpDataService.cs` (3516 righe) è troppo grande per essere un unico servizio. Proposta di scomposizione: | Servizio | Responsabilità | Metodi | |----------|---------------|--------| | `MpDataService` (core) | Gateway, orchestrazione | ~50 | | `IodlManagementService` | ODL lifecycle (apri, chiudi, split, conferma) | ~15 | | `FluxLogProcessingService` | Processing flux log, snapshot, pareto | ~10 | | `MachineCommunicationService` | Keep-alive, dati macchin, micro-stati, state machine | ~20 | | `ProductionTrackingService` | Conferma produzione, pezzi prodotti, stato produzione | ~8 | | `RedisCacheService` | Wrapper methods per Redis operations (Hash, String, Set) | ~15 | **Nota:** La scomposizione va fatta **dopo** la migrazione a repository (Fase 3) per non dover modificare ogni singola chiamata. ### Fase 5: Build & Verifica ✅ Dopo ogni fase completa: 1. `dotnet build MP-IOC.sln` → ✅ 0 errori, 12 warnings (tutti preesistenti) 2. `dotnet build MP-SPEC.sln` → ✅ OK 3. `dotnet build MP-LAND.sln` → ✅ OK 4. `dotnet build MP-MON.sln` → ✅ OK ## Build Status Aggiornato | Fase | File Modificati | Righe Modificate | Rischio | Stato | |------|-----------------|-------------------|---------|-------| | F1: Anti-Pattern 4 controller | 4 Controllers | ~20 | Basso | ✅ **Completato** | | F2: FusionCache config + metodi | 2 Files + Helper | ~250 | Basso | ✅ **Completato** | | F3: Repository IoC | 7+ nuovi + MpDataService | ~2000 | Alto | ⏳ Da fare | | F4: Scomposizione MpDataService | 5 nuovi + 1 esistente | ~3516 | Alto | ⏳ Da fare | | F5: Build & Verifica | — | — | Basso | ✅ 0 errori | ## Stato Completato ### ✅ Fase 1: Anti-Pattern Controller — COMPLETATA Rimosso `static IConfiguration _configuration` da tutti i 4 controller. ### ✅ Fase 2: FusionCache + Redis Backplane — COMPLETATA - Pacchetti FusionCache aggiunti a `MP.IOC.csproj` - `IFusionCache` configurato con L1 Memory + L2 Redis + Backplane in `Program.cs` - Campo `_cache` aggiunto a `MpDataService` con helper `GetOrFetchAsync` - **18 metodi migrati** dal pattern manuale Redis → FusionCache - Build: ✅ **0 errori**, 12 warnings (tutti nullable reference preesistenti) ## Invntario Completo Metodi (Post-Refactoring) ### ✅ MIGRATI A FUSIONCACHE (18 metodi) | # | Metodo | Fetch From | Tag | |---|--------|------------|-----| | 1 | `AnagStatiGetAllAsync` | `IocDbController.AnagStatiGetAllAsync()` | `Utils.redisAnagStati` | | 2 | `ArticoliGetLastByMaccAsync` | `IocDbController.ArticoliGetLastByMaccAsync()` | `Utils.redisArtList:Last:{idxMacc}` | | 3 | `ConfigGetAllAsync` | `IocDbController.ConfigGetAllAsync()` | `Utils.redisConfKey` | | 4 | `DecNumArtGetFiltAsync` | `IocDbController.DecNumArtGetFiltAsync()` | `Utils.redisDecNumArtKey:{tag}` | | 5 | `DossierLastByMachAsync` | `IocDbController.DossGetLastByMaccAsync()` | `Utils.redisDossByMacLast:{idxMacc}` | | 6 | `GetLastOdlAsync` | `IocDbController.OdlLastByMaccAsync()` | `Utils.redisOdlLastByMac:{idxMacc}` | | 7 | `ListValuesFilt` | `IocDbController.ListValuesFiltAsync()` | `Utils.redisConfFlux:{tag}` | | 8 | `Macchine2SlaveGetAllAsync` | `IocDbController.Macchine2SlaveAsync()` | `Utils.redisBaseAddr:M2STab` | | 9 | `MacchineGetFilt` | `_productionRepository.MacchineGetFiltAsync()` | `Utils.redisMacList:{keyGrp}` | | 10 | `MacchineRecipeArchive` | `_productionRepository.MacchineGetFiltAsync("*")` → LINQ | `Utils.redisMacRecipePath:{idxMacc}` | | 11 | `MseGetAllAsync` | `IocDbController.MseGetAllAsync(maxAge)` | `Constants.redisMseKey` (+ forceDb) | | 12 | `OdlCurrByMaccAsync` | `IocDbController.OdlCurrByMaccAsync()` | `Utils.redisOdlList:Current:{idxMacc}` | | 13 | `POdlGetByKey` | `_productionRepository.PODL_getByKeyAsync()` | `Utils.redisPOdlByPOdl:{idxPODL}` | | 14 | `POdlGetByMaccArtAsync` | `IocDbController.POdlGetByMaccArtAsync()` | `Utils.redisPOdlByMaccArt:{idxMacc}...` | | 15 | `ConfFluxMach` | `IocDbController.ConfFluxFiltAsync()` → LINQ | `Utils.redisConfFlux:{tag}` | | 16 | `FluxLogFirstByMachAsync` | `IocDbController.FluxLogFirstByMaccAsync()` → LINQ | `Utils.redisFluxLogFirstByMac:{key}` | | 17 | `FluxLogGetLastByMachAsync` | `IocDbController.FluxLogGetLastFiltAsync()` → LINQ | `Utils.redisFluxLogByMac:{key}` | ### ❌ NON MIGRATI (Scrittura / TTL / Hash-only — 30+ metodi) Questi metodi usano Redis ma **NON** hanno pattern cache-aside (StringGet → DB → StringSet). Sono dati transituali IOB o write-through: | Categoria | Metodi | Motivazione | |-----------|--------|-------------| | **Scrittura Only (StringSet)** | `MachineParamListSetAsync`, `SaveMachine2Iob`, `SaveMachineIobConf`, `SetIobConfYamlAsync`, `SetIobMemMap`, `UpsertCurrObjItemsAsync` | Solo StringSet, zero StringGet | | **Lock/TTL-based** | `AutoStartOdlAsync` (veto), `ScriviKeepAliveAsync` (TTL 30s) | Pattern di lock, non di cache | | **Hash-based IOB State** | `MachineParamListAsync`, `AddCheckTask4Machine`, `AddOptPar4Machine`, `AddTask4Machine`, `AddTask4MacListAsync`, `mOptParMacchina`, `mSavedTaskMacchina`, `mTaskMacchina`, `mTaskMacchinaAsync`, `mDatiMacchineAsync`, `mTabMSMIAsync`, `StateMachInByKeyAsync`, `ValoreSmiAsync`, `StateMachInByKeyAsync` | Redis Hash per dati in tempo reale IOB (transazionali), non Database-backed | | **Hash Reset Helpers** | `ResetDatiMacchinaAsync`, `resetMSMIAsync`, `resetSMIAsync` | Reset di tabelle Hash Redis, non cache | | **Redis GetHash Helpers** | `RedisGetHash`, `RedisGetHashAsync`, `RedisGetHashDict`, `RedisGetHashDictAsync`, `RedisSetHash`, `RedisSetHashAsync`, `RedisSetHashDict`, `RedisSetHashDictAsync`, `RedisCountKey`, `RedisDelKey`, `RedisKeyPresent`, `RedisGetHashField` | Helper utility Redis, non metodi di business | ### ⚠️ DA VERIFICARE (Pattern atipico - 4 metodi) Questi metodi hanno Redis ma il pattern non è lo standard cache-aside. Vanno analizzati caso per caso: | # | Metodo | Riga | Motivo Verifica | |---|--------|------|-----------------| | 1 | `GetCurrOdlAsync` | ~892 | Chiama `GetCurrOdlByProdAsync()` (metodo interno complesso), non `IocDbController.XXX()` direttamente. Pattern: StringGet → DB → StringSet. Verificare se `GetCurrOdlByProdAsync` è una stored procedure. | | 2 | `StatoProdMacchinaAsync` (private) | ~3267 | Ha parametro `forceDb`, cache breve (60s pattern). Pattern valido ma private. | | 3 | `verificaIdxMacchinaAsync` | ~3267 | Verifica esistenza macchina, non un cache-aside standard. | | 4 | `ListMasterAsync` / `ListSlaveAsync` | ~2820/~2840 | Cache-aside valido ma derivano da `Macchine2SlaveGetAllAsync()` (già migrata). Potrebbero essere rimossi in favore di un unico repository. | ## Priorità Operativa 1. ~~**F1 immediata**~~ — ✅ Completata 2. ~~**F2 FusionCache**~~ — ✅ Completata (18 metodi migrati) 3. **F3 Repository IoC** — Prossimo step maggiore: decomporre `MpIocController` (1480 righe, 82 metodi) in 7-10 repository specialistici 4. **F4 Scomposizione MpDataService** — Quando Fase 3 è stabile, dividere il monolite da 3516 righe 5. **Verifica metodi atipici** — I 4 metodi con pattern non-standards vanno valutati manualmente ## Note - MpIocController usa già `IDbContextFactory` correttamente — il problema è che è un Singleton usato da un altro Singleton (MpDataService) - `IIocRepository` (già esistente) ha solo 16 metodi ed è mai usato da MpDataService - I repository SPEC (Anag, Dossier, FluxLog, Production) sono già usati da alcuni metodi — sfruttarli dove possibile - La struttura `MP.Data/Controllers/` è ingannevole: `MpIocController` è un servizio, non un controller Web API - `redisConnAdmin` (connessione Redis admin) sembra non essere mai usata — verificare eliminazione - **Build finale:** ✅ 0 errori, 12 warnings (tutti nullable reference preesistenti)