15 KiB
Piano di Refactoring: MP.IOC
Obiettivo
Modernizzare il progetto MP.IOC allineandolo agli standard architettonici definiti in MP.SPEC:
- Rimuovere l'uso di
MpIocControllercome servizio di accesso dati diretto - Migrare la logica di
MpDataServiceai repository esistenti (IIocRepository,IStatsAggrRepository, etc.) - Standardizzare il caching manuale Redis → FusionCache
GetOrFetchAsync - 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
- 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
- MpDataService bypassa repository — ~80% dei metodi chiama
IocDbController.XXX()direttamente invece di usareIIocRepository - Caching misto — Parte migrato a FusionCache, parte ancora in manual StringSet/StringGet
- 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)
// 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:
dotnet build MP-IOC.sln→ ✅ 0 errori, 12 warnings (tutti preesistenti)dotnet build MP-SPEC.sln→ ✅ OKdotnet build MP-LAND.sln→ ✅ OKdotnet 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 IFusionCacheconfigurato con L1 Memory + L2 Redis + Backplane inProgram.cs- Campo
_cacheaggiunto aMpDataServicecon helperGetOrFetchAsync<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
F1 immediata— ✅ CompletataF2 FusionCache— ✅ Completata (18 metodi migrati)- F3 Repository IoC — Prossimo step maggiore: decomporre
MpIocController(1480 righe, 82 metodi) in 7-10 repository specialistici - F4 Scomposizione MpDataService — Quando Fase 3 è stabile, dividere il monolite da 3516 righe
- Verifica metodi atipici — I 4 metodi con pattern non-standards vanno valutati manualmente
Note
- MpIocController usa già
IDbContextFactorycorrettamente — 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)