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

15 KiB

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)

// 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)