Architettura Smart Data e Sincronizzazione Stato

Contesto

Durante lo sviluppo della funzionalità per il toggle “ADJ/RAW” (visualizzazione dati rettificati vs grezzi), è emerso un bug critico di “Split-Brain” nell’applicazione FIRE. Il sintomo era che la UI aggiornava correttamente la configurazione in memoria, ma i Worker asincroni (lanciati in thread separati) continuavano a leggere una configurazione vecchia, rendendo inefficace il toggle.

L’analisi ha rivelato che la causa radice era la presenza di istanze multiple non sincronizzate di SettingsManager, create implicitamente da diversi componenti (AppState vs PanelFactory).

Inoltre, per soddisfare i requisiti di un software finanziario professionale, è emersa la necessità di gestire i dati storici con un approccio “Sovrano” (Smart Data), garantendo la disponibilità dei dati RAW originali e applicando le rettifiche solo su richiesta.

Alternative Considerate

1. Dependency Injection Pura (Strict DI)

  • Approccio: Creare un’unica istanza di configurazione nel main.py e passarla a cascata (tramite costruttori) a tutti i componenti fino alle foglie (Worker).
  • Pro: Massima testabilità, architettura esplicita, eliminazione dello stato globale.
  • Contro: In un’applicazione Qt complessa con gerarchie profonde, richiede un refactoring massivo (“Big Bang”) e introduce molto codice boilerplate (“passacarte”).

2. Event Driven (Signals & Slots)

  • Approccio: Mantenere istanze separate ma sincronizzarle tramite segnali Qt quando lo stato cambia.
  • Pro: Disaccoppiamento elevato.
  • Contro: Over-engineering per la gestione di configurazioni statiche. Difficile tracciare il flusso di aggiornamento (Debugging complesso).

3. Singleton Pattern (Pragmatico)

  • Approccio: Trasformare SettingsManager in un Singleton, garantendo che SettingsManager() restituisca sempre la stessa istanza condivisa.
  • Pro: Risoluzione immediata del disallineamento, riduzione del codice di passaggio parametri.
  • Contro: Introduce stato globale (“Magia”), rischio di race conditions se i Worker leggono lo stato mentre la UI lo scrive.

Decisione: Strategia Ibrida (Singleton + Snapshot)

Abbiamo deciso di adottare un approccio ibrido che bilancia il pragmatismo del framework Qt con il rigore richiesto dal dominio finanziario.

1. Singleton Thread-Safe per la Configurazione

Abbiamo trasformato SettingsManager in un Singleton (usando __new__ e threading.Lock).

  • Motivazione: Garantisce l’esistenza di un’unica “Fonte di Verità” (Single Source of Truth) in tutta l’applicazione, risolvendo il problema delle istanze “ombra” senza richiedere la riscrittura di ogni singolo costruttore UI.

2. Composition Root DI

Nonostante il Singleton, nel punto di ingresso (main.py) adottiamo comunque la Dependency Injection esplicita per i componenti core (AppState, DataManager).

  • Motivazione: Mantiene chiaro il grafo delle dipendenze di alto livello e facilita l’inizializzazione ordinata.

3. Worker Snapshot Pattern (Componente Critico)

Abbiamo stabilito che i Worker asincroni NON DEVONO MAI accedere direttamente allo stato globale (SettingsManager) durante la loro esecuzione. Invece, devono ricevere una copia congelata (Snapshot) dei valori di configurazione necessari al momento della loro istanziazione (__init__).

  • Esempio: Il ChartDataWorker riceve use_adjusted: bool nel costruttore.
  • Motivazione:
    • Determinismo: Il comportamento del worker è fissato alla nascita.
    • Thread Safety: Elimina alla radice le race conditions (lettura/scrittura concorrente) senza bisogno di Lock complessi.
    • Auditability: È possibile loggare esattamente con quali parametri il worker è stato lanciato.

4. Pipeline “Smart Data”

Contestualmente, abbiamo definito lo standard per la persistenza dei dati:

  • Storage RAW: I dati vengono salvati su disco in formato RAW. Se il provider (es. Yahoo) invia dati rettificati, applichiamo una “Reverse Adjustment” prima del salvataggio.
  • Sidecar Metadata: Ogni file dati è accompagnato da un .meta.json che contiene gli eventi (Split, Dividendi) e la natura del dato.
  • Runtime Calculation: La rettifica dei prezzi avviene in memoria (DataManager) solo se l’utente la richiede e se i metadati lo consentono.

Conseguenze

  • Positivo: Eliminazione definitiva dei bug di sincronizzazione stato UI/Worker.
  • Positivo: Garanzia di riproducibilità e tracciabilità delle operazioni finanziarie (grazie allo Snapshot).
  • Positivo: Flessibilità totale nella visualizzazione dei dati (Raw vs Adjusted) senza latenza di rete (lettura intelligente della cache).
  • Negativo: L’uso del Singleton richiede disciplina per evitare che venga abusato per passare stati volatili tra componenti non correlati.

Compliance

Tutti i futuri sviluppi che coinvolgono Worker asincroni sensibili alla configurazione DEVONO implementare il Snapshot Pattern. È vietato leggere SettingsManager all’interno del metodo run() di un QRunnable o QThread.