Applicazioni distribuite
Per seguire questa lezione è fortemente raccomandato di aver approfondito il concetto di architetture visto in questa lezione.
Applicazione monolitica vs applicazione distribuita
Prendiamo prima di tutto la definizione di questi due approcci alla realizzazione di applicazioni.
- Applicazione monolitica: applicazione che in fase di build va a distribuire un unico eseguibile che girerà su una unica macchina.
- Applicazione distribuita: applicazione che è costituita da diversi eseguibili che girano su macchine differenti connesse tra loro in rete (Internet o intranet).
Il modello di una applicazione distribuita è ad esempio quello della seguente figura (applicazione con architettura client-server SOA) .

La scelta che guida se una applicazione deve essere distribuita o monolitica è legata alla gestione e la manipolazione dell’informazione, ma anche esigenze di sicurezza (dove collocare i dati), prestazioni delle macchine (il carico di lavoro più pesante viene dato alla macchina più costosa ma anche prestazionale), scalabilità (accrescere le risorse hardware senza toccare tutta l’applicazione).
Se l’intera gestione dell’informazione è sulla macchina locale, e l’interazione con macchine remote, se esistente, è accessoria, si ha una applicazione monolitica locale. Un esempio di questo tipo di applicazioni è la suite Office.
Se l’intera gestione è invece remota, e l’utente interagisce localmente solo con un terminale (anche web) che non svolge nessuna attività di manipolazione, allora l’applicazione è monolitica remota. Un esempio di questo tipo di applicazioni sono i siti web dinamici ma anche statici.
Se invece la gestione dell’informazione è distribuita, e quindi sia nella macchina locale che in quella remota sono svolte attività significativa di manipolazione dell’informazione, allora l’applicazione è distribuita. Un esempio di questo tipo di applicazioni sono le web applications.
Il tipo di applicazione distribuita può essere poi basato su una architettura client-server oppure peer-to-peer (molte macchine che lavorano insieme per un unico obiettivo, come ad esempio i motori di ricerca, i software di blockchain, gli LLM, ecc.)
Ecco tre scenari di utilizzo distribuito in un contesto client-server:
- Poter gestire processi/informazioni/algoritmi complessi su una macchina remota e le logiche di interazione e visualizzazione sulla macchina dell’utente: un esempio di questo sono le applicazioni di home banking.
- Poter gestire grandi moli di dati su una macchina remota, e l’intera logica applicativa sulla macchina dell’utente: un esemp-io di questo sono applicazioni di posta elettronica online (ad esempio Gmail);
- Poter eseguire applicazioni molto complesse su server, e lasciare al terminale utente il solo compito di gestire la visualizzazione dei dati e l’interazione con l’utente: un esempio di questo è il terminale Bancomat o siti di ecommerce come Amazon.
Sono possibili però anche scenari peer-to-peer;
- Più componenti che collaborano tra loro per distribuire una operazione di calcolo complessa, o condividere grandi moli di dati: ad esempio i motori di ricerca;
- Più componenti che collaborano tra loro per gestire molti utenti: ad esempio i server web.
In questa sede ci concentreremo in particolare sulle applicazioni distribuite basate su architettura client-server su Internet.
Ripasso dell’architettura a layer
Per capire come funziona una applicazione distribuita bisogna tenere ben presenti i concetti chiave di architettura del software. Come abbiamo già visto la maggior parte delle applicazioni si tende a progettarle in componenti e moduli che si basano sul concetto di layer e di separazione delle responsabilità.
Questo concetto consiste dividere l’applicazione in strati di componenti omogenei (detti layer) ognuno con un proprio ruolo e con la capacità di comunicare limitatamente con gli strati adiacenti.
L’obiettivo quello di è semplificare lo sviluppo e suddividere la complessità secondo il modello “divide et impera”.
Tra i modelli basati su layer uno dei più diffusi è basato sui seguenti layers:
- Un layer di accesso ai dati/documenti, denominato “data access”, che contiene le funzioni di accesso ai dati, e il loro adattamento al media utilizzato (ad esempio la trasformazione del modello dati in una query SQL o in un tracciato XML). Siccome l’obiettivo è gestire il salvataggio su strutture di dati persistenti (database) si parla di “persistence layer”.
- un layer di manipolazione dei dati, denominato “business logic”, che contiene il modello concenttuale dei dati (ad esempio tramite classi ed oggetti) e le logiche applicative. Questo layer si occupa di creare un modello concettuale e manipolarlo, e si usa anche il termine “Domain Model” che rappresenta il “dominio” dell’informazione.
- Un layer di presentazione dei dati all’utente, che elabora le informazioni, le filtra e le adatta alla visualizzazione/consumo dell’utente. Inoltre le azioni dell’utente che vengono dall’interfaccia di visualizzazione vengono preelaborate, validate e filtrate. Questo layer fa da tramite tra il livello logico e quello di visualizzazione.
- Un layer di visualizzazione dei dati, che in genera ha poca o nessuna logica, e si occupa di mostrare all’utenti i contenuti ed interagire con loro (ad esempio tramite una pagina web).
In figura la schematizzazione di questo modello, in una applicazione monolitica.

Modelli di architetture distribuite a layer
Con questa tipologia di divisione interna è possibile progettare una versione distribuita, semplicemente separando uno o più layer in applicazioni separate connesse tra loro in rete.
Qui i tre esempi descritti in precedenza:
- Caso home banking: la gestione dei dati e le business logic sono interamente lato server, mentre la presentazione e la visualizzazione sono a carico del client.

- Caso Gmail: in questo caso anche la logica di business è sviluppata lato client.

- Caso Amazon/CMS/Web dinamico: l’applicazione lato client contiene poco o nulla a livello di codice ad alto livello se non la semplice validazione dei dati e poco altro.

Riepilogo
Quali sono i vantaggi delle applicazioni distribuite?
- È possibile suddividere il lavoro assegnando due team che possono operare in parallelo sui due prodotti;
- È possibile scrivere una applicazione anche con logiche molto sofisticate che gira correttamente anche su client poco potenti, perché l’elaborazione avviene altrove;
- È possibile centralizzare la sicurezza;
- E’ possibile modificare una porzione dell’applicazione in modo indipendente dall’altra;
- E’ possibile usare tecnologie differenti;
- E’ possibile utilizzare team differenti in luoghi differenti
Quali sono gli svantaggi?
- Aumenta la complessità;
- Va scritto un middleware (vedi sotto) per ogni connessione
Una applicazione distribuita permette di realizzare scenari più articolati dove allo stesso backend possono corrispondere più frontend.
Qui un esempio in stile “home banking” dove però abbiamo un client web e un client mobile.

Il middleware
Il middleware è lo strato software che rende possibile la collaborazione tra due componenti remoti della stessa applicazione distribuita. Il middleware è formato da componenti che insieme svolgono il ruolo di:
- intermediazione tra componenti software differenti, e non necessariamente sulla stessa macchina
- strumento attraverso sui avviene la comunicazione di comandi e dati
- metodi e strumenti per l’interrogazione, la risposta e lo scambio dati.
Qui un esempio

L’obiettivo del middleware è fornire una interfaccia di programmazione (detta API) che simula il comportamento del componente esterno come se fosse interno.
Nell’esempio sopra indicato:
- il middleware che connette il persistence layer al database fornisce una API per interrogare il database, cioè un insieme di classi ed oggetti che internamente implementano la chiamata al database e le operazioni come query di inserimento o caricamento;
- il middleware che connette il budiness logic layer al presentation layer crea lato server una API tramite connessione http che permette al client, di interagire con lo strato adiacente che però si trova sul server;
Il middleware è efficace quando riesce a “nascondere” l’esistenza di una applicazione esterna e a garantire allo sviluppatore la medesima coerenza di sviluppo che avrebbe interagendo con un componente locale.
Architettura a microservizi
Questo tipo di applicazioni prevede in genere un unico eseguibile per gestire tutte le funzioni, in cui i moduli sono organizzati in layer ognuno responsabile di una parte dell’applicazione, come l’interfaccia API, la presentazione delle informazioni, la gestione dei dati, ecc.
Un applicativo di ecommerce per esempio ha una funzione per gestire l’autenticazione utente, una funzione per gestire il catalogo ed una per gestire gli ordini. Ma l’applicativo resta solo uno, e ogni funzione “attraversa” i layer applicativi dove ogni layer (middleware, presentazione, business logic) prevederà sicuramente componenti di servizio usati dai componenti che implementano le singole funzionalità.
Questo modello monolitico prevede quindi un ciclo di sviluppo, rilascio e supporto unico. Se ad esempio si scopre che la gestione ordini ha un bug, quando viene sistemato bisogna rilasciare l’intera applicazione.
Come abbiamo accennato nella lezione sulle architetture, potrebbe avere senso invece un approccio dove ciascuna delle funzioni è racchiusa in un modulo indipendente, ognuno coi suoi layer, e gestire ognuno dei moduli come una mini applicazione indipendente.
Per il programmatore non è pratico suddividere l’applicazione in tante mini applicazioni perchè introduce complessità, infatti i componenti condivisi tra diverse funzioni andrebbero replicati. Inoltre andrebbero realizzate tante build in parallelo, da testare separatamente. Tuttavia con l’avvento della containerizzazione questo problema è in gran parte risolto, perchè è più semplice gestire tramite container molte mini applicazioni al posto di una sola.
Una applicazione che fornisce servizi web tramite mini applicazioni si chiama applicazione per microservizi. Nel nostro esempio avremo un microservizio di autenticazione, uno di catalogo ed uno di ordini. Il loro sviluppo e rilascio è indipendente perché sono applicazioni indipendenti. Ma siccome il contesto di esecuzione è in container collegati essi vengono eseguiti comunque in modo interdipendente e collegato tra loro. Con questo sistema il programmatore semplifica lo sviluppo, riduce le interconnessioni tra moduli (“basso accoppiamento”), e può migliorare o correggere errori di un solo microservizio senza rilasciare tutta l’applicazione. L’altro grande vantaggio è quello di poter gestire la scalabilità in modo molto più granulare: ad esempio il servizio di login richiede meno risorse del servizio catalogo (un utente fa login una sola volta ma vede poi molte pagine). Qui uno schema di una architettura con due microservizi.

Nelle applicazioni più complesse può diventare complicato gestire il rilascio di gruppi consistenti di microservizi e per questo è necessario predisporre strumenti per la loro gestione. Ad esempio uno dei prodotti più noti è Kubernetes, uno strumento che permette di gestire, attivare e avviare molti microservizi insieme, e tramite configurazione mantenere funzionante il sistema attivando e disattivando cluster di microservizi in modo dinamico.