• L’Architettura Ideale di una Web App: Riflessioni su Data Manager, UI, Layout e Coordinator

    L’Architettura Ideale di una Web App: Riflessioni su Data Manager, UI, Layout e Coordinator

    [Tempo di lettura: 7 minuti]

    Panoramica dell’Architettura Ideale

    Considerate le caratteristiche di: HTML, CSS, JavaScript, con particolare attenzione alla comunicazione asincrona in JavaScript via HTTP, ho lungamente riflettuto su come strutturare il codice di una web app affinché sia manutenibile ed efficiente.

    L’architettura che ho concepito si articola in:

    • una fonte dati esterna con cui comunicare via HTTP API, che chiameremo: remoteStore;
    • la memoria locale a disposizione del browser come previsto in HTTP 5, il localStore;
    • la memoria di lavoro, in RAM, che chiameremo: workingStore;
    • un oggetto dataManager che ha il compito di mantenere allineati i tre data store;
    • vari oggetti di classe UI (User Interface), che rappresentano elementi HTML a disposizione dell’utente per consultare i dati ed impartire comandi;
    • un oggetto layoutManager, avente il compito di posizionare gli oggetti UI nella pagina web;
    • un oggetto coordinator, che veicola le interazioni tra tutti gli altri oggetti in base alle specifiche dei vari casi d’uso previsti, basandosi su una coda di eventi.

    Nel seguito, illustro il funzionamento, le funzionalità e la collocazione di una struttura così architettata.

    Il Ruolo Centrale del Data Manager nella Sincronizzazione dei Dati

    Il dataManager contatta periodicamente il remoteStore per verificare se ci sono variazioni dei dati. In tal caso, applica le differenze al workingStore gestendo eventuali conflitti, cioè variazioni avvenute in parallelo sia nel remoteStore sia nel workingStore.

    Per la valutazione automatica su quali siano le differenze da applicare, si usa un meccanismo di assegnazione di doppia chiave per ciascun record: chiave assegnata dall’applicazione e chiave assegnata dal remoteStore.

    • Se un record ha solo una delle due chiavi allora sappiamo che va inserito nello store corrispondente a quello della chiave mancante.
    • Se un record ha entrambe le chiavi, allora significa che c’è una variazione del dato da applicare.

    Il dataManager ha un importante ruolo semantico: la struttura dei dati provenienti dal remoteStore è potenzialmente differente da quella del workingStore ed il dataManager ha il compito di tradurre i dati dall’una all’altra e viceversa.

    Anche l’allineamento tra workingStore e localStore è di competenza del dataManager ma è più semplice. Fatta eccezione per una chiave di autenticazione dell’installazione del browser depositata nel localStore, gli altri dati vengono generalmente sovrascritti, in modo che nel localStore ci sia una copia del working Store.

    Un ultimo importante aspetto è che il dataManager effettua allineamenti su richiesta del coordinator. Supponiamo per esempio che si debba presentare all’utente una lista che ha potenzialmente migliaia di voci, corrispondenti a schede articolate in numerosi campi. Il dataManager riceverà solo i dati utili per preparare la lista, senza tutti i dettagli dei vari elementi. Quando l’utente seleziona un elemento, il coordinator può chiedere al dataManager di recuperare tutti i dettagli corrispondenti.

    Componenti UI e i Loro Flussi di Input/Output

    Un componente della UI ha funzione tipica di I/O:

    • checkbox;
    • radio button;
    • testo statico;
    • selezione da un elenco…

    Ciascun componente viene creato nella pagina con un ben preciso ID che lo individua univocamente nella pagina HTML. Assegnandogli uno o più event listener, il componente UI potrà acquisire dati. Questi saranno poi trasmessi al coordinator, che li metterà in coda.

    I componenti UI vengono attivati, disattivati e collocati dal layoutManager, mentre il coordinator può inviare loro segnali di riconfigurazione. Per esempio, se un dato acquisito porta ad uno stato non valido, il coordinator invia al componente UI che l’ha acquisito una reazione, in modo che il componente segnali all’utente l’errore commesso.

    Si noti che le trasmissioni da componente UI a coordinator devono sempre essere qualificate utilizzando delle etichette, assegnate dal coordinator stesso in fase di creazione dell’istanza della classe del componente UI.

    Il Layout Manager: Disporre i Componenti dell’Interfaccia Utente

    Basandosi su un costrutto JSON, il layoutManager prevede una serie di componenti della UI, istanziati dal coordinator. Il costrutto JSON indica in che ordine disporre i vari componenti UI, pagina per pagina.

    Nulla toglie che il funzionamento del layoutManager sia parametrizzato. Per esempio, si potrebbe tener conto delle preferenze dell’utente o del profilo di autorizzazione nel determinare l’ordine di alcuni componenti o se nasconderne alcuni.

    Il Coordinator: Gestire l’Interscambio di Messaggi con una Coda di Eventi

    Il primo evento è sempre l’allestimento della prima pagina. Il coordinator istanzia i componenti della UI, li passa al layoutManager che andrà a disporli, impostando posizione ed ingombro di ciascuno. A quel punto, dai vari componenti UI potranno provenire dei segnali. Per esempio, l’utente potrà effettuare una selezione da un menù. Siccome il layoutManager aveva indicato una ben precisa etichetta per il dato prodotto da tale menù, il layoutManager può identificare il caso d’uso ed attivare i passi del corrispondente scenario.

    Ricordiamo che c’è asincronia tra l’azione del dataManager e le azioni dei vari componenti UI. Dunque il coordinator non elaborerà lo stimolo proveniente dal menù se non dopo averlo inserito in una coda con altri eventuali stimoli in attesa di essere elaborati.

    In base ai passi del caso d’uso, il coordinator potrà effettuare vari tipi di azioni:

    • inviare ad uno o più componenti UI un segnale di riconfigurazione (es. l’utente ha selezionato la regione e di conseguenza il menù delle province può essere filtrato);
    • modificare il workingStore ed eventualmente segnalare al dataManager la necessità di sincronizzare i dati con il remoteStore;
    • segnalare al dataManager di memorizzare lo stato di calcolo nel localStore;
    • verificare la coerenza dei dati memorizzati nel workingStore ed eventualmente inviare feedback ai componenti UI interessati;
    • interrompere la sessione.

    Esempio: tempistiche di esecuzione di un compito

    Qual è la tempistica dell’esecuzione di un compito? È una questione che molte persone affrontano ripetutamente più volte al giorno, a partire da consulenti, specialisti, tecnici, addetti al supporto etc. La risposta non è univoca come potrebbe sembrare di primo acchito. Il lavoro può essere eseguito in una o più sessioni, da una o più persone. Di sicuro ha un inizio ed una fine, anche per attività ripetute o periodiche, per le quali si può sempre considerare l’impegno totale finora dedicato.

    Il fatto che più persone o risorse siano state impegnate si risolve in modo ovvio con una sommatoria. Resta la questione del tempo effettivamente dedicato allo svolgimento del compito o, più importante ancora, della durata convenzionale, usata per misurare l’impegno profuso e spesso indicato come: “tempo o durata da fatturare”.

    Consideriamo infine che, dovendo registrare tale misura, si terrà necessariamente conto del lasso di tempo in cui l’attività si è sviluppata, determinato registrando l’inizio e la fine o l’inizio e la durata o, qualche volta, la fine e la durata.

    In sintesi, abbiamo una rete con quattro ingressi:

    • istante di inizio attività;
    • istante di fine attività;
    • durata del lasso di tempo in cui si è sviluppata l’attività, o durata reale, che abbrevieremo con Δt;
    • durata di lavoro o sforzo, cioè tempo effettivamente dedicato all’attività;
    • durata convenzionale o dichiarata;

    e le medesime uscite ma all’interno della rete immaginiamo un miscelatore attivato ogni volta che varia uno degli ingressi. Ciò che conta, è che sia nota la durata dichiarata o, in mancanza, la durata reale o quella di sforzo.

    Se fotografiamo la situazione nell’istante in cui c’è una variazione, ci troviamo necessariamente di fronte ad una sola possibilità tra le varie combinazioni possibili determinate da quanti e quali sono i valori impostati. Per esempio:

    • è stato indicato solo un valore ed è quello di Δt oppure quello della durata convenzionale: in questi casi, non c’è nulla da fare perché la misura dell’impegno, in un modo o nell’altro, c’è;
    • è stato indicato solo l’istante di inizio o solo quello di fine attività: si resta in attesa che venga inserito uno degli altri tre valori e se ci si chiede quale sia la durata totale in questo momento si può solo rispondere che è indeterminata;
    • sono stati indicati due valori, di cui uno è la durata dichiarata o quella di sforzo: si può rispondere alla domanda sulla misura dell’impegno ma non si può sapere in quale intervallo l’attività si è sviluppata;
    • sono stati indicati due, ma non si tratta della durata convenzionale: dunque, si può ricavare il terzo valore mancante tra: inizio, fine e Δt, per esempio si può calcolare la fine avendo inizio e Δt.

    Si potrebbe aumentare la complessità ammettendo che vengano forniti valori anche quando è possibile derivarli da altri già forniti. In tal caso, potrebbe succedere che i valori forniti risultino incoerenti. Tuttavia, questa casistica si può escludere facilmente immaginando che la procedura proposta venga eseguita, come inizialmente indicato, ad ogni variazione di ciascuno degli operandi e che blocchi l’inserimento di valori derivabili in modo univoco tramite calcolo.

    Orbene, come interagiranno tra loro li coordinator ed i componenti UI nella web app per gestire una casistica del genere?

    Immaginiamo che la nostra app comprenda cinque campi di input.

    Ecco qualche passaggio, a partire da quando la UI è stata preparata e l’utente si accinge ad inserire dati. Ci sono 5 listener, uno per ciascuno dei campi di input.

    L’utente inserisce l’inizio attività. Il sistema si trova solo con quel dato mentre gli altri sono indeterminati e, in base alle regole, non fa nulla.

    L’utente inserisce la durata totale. Il sistema calcola l’istante di fine attività sommando l’inizio con la durata. L’utente inserisce anche la durata di sforzo e, successivamente, quella dichiarata.

    Approfondiamo cosa succede quando il sistema calcola la fine attività. Il componente UI della fine attività, ad inserimento completato, invia al coordinatore il valore inserito, con opportuna etichetta che identifica univocamente questo caso d’uso. Il coordinatore riceve il dato, verifica la presenza di fine o durata reale nel workingStore. coordinator trova la durata e segue le regole di calcolo che prevedono, in casi come questo, di derivare la durata reale.

    Conclusioni: Verso una Web App più Strutturata ed Efficiente

    Il ruolo delle etichette non deve passare inosservato. È cruciale dal punto di vista semantico, sia in fase di traduzione tra remoteStore e workingStore sia, come indicato al primo paragrafo della sezione precedente, in fase di interazione con l’utente.

    Questo approccio ontologico conferisce al codice di programmazione maggiore intelleggibilità e coerenza rispetto ai casi d’uso, gestiti in modo centralizzato dentro il coordinator.

    L’assenza di interazioni dirette tra componenti UI consente di manutenzionare o sostituire singoli componenti UI senza doversi occupare di errori di regressione. È infatti proprio l’intreccio tra componenti di una pagina la maggior fonte di questo tipo di problemi.

    La fatica di predisporre un dataManager è ampiamente ripagata dalla possibilità di intervenire in modo chirurgico qualora la fonte dati esterna venga aggiornata. Al contempo, si garantisce comfort e stabilità per i programmatori che devono realizzare i componenti UI.

    Il fatto di disporre di un layoutManager si presta alla personalizzazione del layout utente per utente. Basta infatti ordinare e filtrare i componenti in base all’utente.

    Infine, l’uso di una coda per la gestione degli eventi assicura coerenza logica dei dati del workingStore.

    Qualora la connessione sia perduta momentaneamente o la sessione venga sospesa dall’utente che ha chiuso il browser, la persistenza nel localStore consente di riprendere il lavoro nel punto in cui l’utente l’aveva lasciato.


    Ti sarei grato se volessi contattarmi per farmi sapere cosa pensi di quest’idea. Se non sei un programmatore, intanto ti faccio subito i miei migliori complimenti per aver tenuto duro fino a qui e se hai qualche dubbio ti prego di non esitare e di contattarmi. I dubbi sono preziosi anche in ottica di miglioramento di questo post.

    Grazie per l’attenzione.

    Video di MD BORHAN UDDIN da Pixabay