Files
mapo-core/MP.IOC/refactor_plan.md
T
2026-06-04 18:23:13 +02:00

236 lines
15 KiB
Markdown

# 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<IDistributedCache>())
.WithSerializer(new FusionCacheNewtonsoftJsonSerializer())
.WithBackplane(new RedisBackplane(new RedisBackplaneOptions {
ConnectionMultiplexerFactory = () => Task.FromResult<IConnectionMultiplexer>(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<T>`
- **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)