(articolo redatto da Vito Carrozza)
I pattern architetturali sono soluzioni strutturate e collaudate per affrontare problemi comuni durante la progettazione di un sistema software. Essi forniscono una guida su come organizzare i componenti, definire le loro responsabilità e le interazioni per ottenere un sistema efficiente, manutenibile e scalabile. L’uso di questi pattern permette di evitare errori già noti, migliorare la comunicazione tra i team di sviluppo e creare soluzioni che possono evolversi facilmente nel tempo.
In questo articolo, esploreremo alcuni dei pattern architetturali più utilizzati nelle moderne architetture software, analizzandone i pro e i contro e valutando i contesti in cui ciascuno di essi può essere adottato con successo.
Architettura Multi-Tier
L’architettura multi-tier (o a più livelli) è un modello che divide un’applicazione in livelli logici distinti detti “tiers”, ciascuno con responsabilità specifiche. Ogni livello comunica con il successivo attraverso interfacce ben definite, creando una struttura modulare che semplifica la gestione del codice e migliora la scalabilità.
Multi-layer vs multi-tier
Spesso si tende a confondere il concetto di “layer” con quello di “tier”, ma tra i due esistono differenze importanti.
Con il termine layer ci si riferisce alla separazione logica del codice in strati che vengono eseguiti nello stesso processo o server. Sotto questa luce, l’intera codebase appare come se fosse un unico monolite.
L’architettura multi-tier, invece, implica una separazione fisica, in cui i vari livelli possono essere distribuiti su server diversi. Questo approccio consente di scalare i singoli livelli in base alle necessità, aumentando l’efficienza del sistema.
Per preservare i parametri di affidabilità e il corretto funzionamento dei servizi, l’architettura multi-tier impone uno specifico flusso di funzionamento che si basa su due regole inderogabili:
- il tier i-esimo comunica solo ed esclusivamente con i suoi adiacenti: non è possibile, ad esempio, passare dal primo al terzo tier
- i tier adiacenti comunicano tra loro usando il paradigma client/server: il tier i-esimo che ha bisogno di una specifica informazione lo fa inviando una richiesta a un tier ad esso adiacente.
Architettura 3-Tier
L’architettura 3-tier è la forma più comune di architettura multi-tier.
È composta da tre tiers così denominati:
- Presentation tier: è responsabile dell’interfaccia utente e della visualizzazione e raccolta dei dati. In questo livello non deve esserci logica di business perché esso gira sulla macchina client.
- Application o Business logic tier: gestisce la logica di business e l’elaborazione dei dati.
- Data Tier: si occupa della memorizzazione e della persistenza.

L’architettura 3-tier è utilizzata per sviluppare applicazioni web tradizionali, sistemi gestionali e applicazioni client-server. Offre una separazione chiara delle responsabilità, il che facilita la manutenzione e permette di aggiornare ogni livello indipendentemente dagli altri.
Tuttavia, l’architettura 3-tier può presentare alcuni svantaggi.
In contesti che richiedono un’elevata scalabilità orizzontale e organizzativa i tier non risultano scalabili facilmente. Ciò è dovuto sostanzialmente a questi fattori:
- lo strato di presentazione, nella maggior parte dei casi, contiene chiari riferimenti alla sessione utente, la quale se viene mantenuta in memoria sui singoli server, il bilanciamento del carico può diventare problematico perché le richieste successive di un utente potrebbero finire su un server diverso (che non ha la sessione in memoria);
- quando si scala orizzontalmente lo strato di business logic aggiungendo nuovi server applicativi, si deve garantire che tutti i server siano sincronizzati e operino correttamente con le stesse versioni del software e dei dati. Ciò significa che deve essere garantita la gestione della concorrenza per evitare situazioni di conflitto tra diverse istanze e la coordinazione tra i servizi distribuiti in modo che lavorino insieme senza creare colli di bottiglia;
- i problemi legati alla scalabilità del data layer sono legati soprattutto a questioni di scalabilità del database, le quali implicano i concetti di condivisione e consistenza dei dati.
Questi problemi si possono agevolmente risolvere utilizzando i blocchi fondamentali di costituzione di un’architettura software come, ad esempio, i load balancer. In tal caso l’architettura, e quindi i rispettivi tier, possono essere scalati trasparentemente senza blocchi o interruzioni di fruizione dei servizi.

La separazione fisica dei livelli, inoltre, può introdurre latenza nella comunicazione tra i server e può risultare complesso scalare il livello della logica di business senza compromettere le performance globali del sistema. Ciò è dovuto al fatto che non esistendo alcuna logica applicativa nel presentation e del data tier, questa è tutta riversata nell’application tier e in questa luce esso appare come un monolite.
Dal punto di vista dell’esecuzione, ciò ha due implicazioni:
- maggiore consumo di cicli di CPU e memoria in presenza di richieste multiple da diversi utenti, specialmente con logica scritta con tecnologie dove le operazioni di garbage collecting sono frequenti (come ad esempio in Java). Ciò porta a porta a rallentamenti e lentezza. Per risolvere questo problema, le macchine possono essere scalate verticalmente;
- velocità di sviluppo lenta: data la struttura monolitica del tier, la scalabilità organizzativa risulta difficilmente applicabile.
Altre architetture multi-tier
Esistono poi diverse varianti dell’architettura multi-tier.
Ad esempio, l’architettura 2-tier, che comprende uno strato di accesso ai dati e uno strato di presentazione che ingloba anche la logica di business, è particolarmente indicata quando è necessario minimizzare l’overhead fra gli strati di presentazione e di manipolazione dei dati.
Esiste poi l’architettura a 4 tiers, in cui il secondo tier ha le funzioni di accesso controllato ai dati e alla logica di business. In questo caso, ci si riferisce a questo strato con il termine di “API Gateway Tier”.
Tutte le altre architetture con un numero di tiers superiore a quattro sono fortemente sconsigliate, in quanto la complessità di gestione e l’overhead di passaggio da un tier ed un altro diventano critici.
Architettura a microservizi
L’architettura a microservizi è un approccio che divide un’applicazione in un insieme di servizi piccoli, autonomi e specializzati, ciascuno dei quali implementa una singola funzionalità.
Ogni microservizio appartiene a un team di dimensioni limitate che ne detiene una parte o tutta la responsabilità.
L’inquadramento dell’architettura sotto questo tipo di pattern architetturale offre numerosi vantaggi in termini di performance, scalabilità e high availability:
- i tre tipi di scalabilità ne risultano avvantaggiati:
- per quanto concerne la scalabilità verticale, una piccola codebase come quella di un microservizio permette all’hardware di non dover essere molto performante e di essere aggiornato con una frequenza minore;
- i microservizi possono essere deployati su più repliche e scalati in maniera trasparente, consentendo così un’agevole scalabilità orizzontale;
- la scalabilità di team risulta facilmente applicabile in quanto ogni modulo/servizio viene sviluppato indipendentemente, sia dal punto di vista tecnico (ad esempio ogni microservizio potrebbe adottare un linguaggio di programmazione diverso per lo sviluppo), sia dal punto di vista dei rilasci (ogni team può decidere tempistiche e metodi di rilascio indipendentemente dallo stato di sviluppo degli altri microservizi).
- le performance migliorano in quanto avere una piccola codbase significa avere un set ristretto di funzionalità, il cui utilizzo è meno gravoso in termini di memoria e CPU
- la separazione dei microservizi migliora la resilienza del sistema, poiché il malfunzionamento di un servizio risulta localizzato, isolabile e non compromette il funzionamento dell’intera applicazione.
Overheads e Sfide
L’adozione dei microservizi, tuttavia, comporta alcune sfide progettuali tra cui:
- Gestione della complessità: la suddivisione in molti servizi introduce complessità nell’orchestrazione e nella gestione delle dipendenze. Inoltre, bisogna fare in modo che i servizi siano logicamente indipendenti in modo tale che ogni cambiamento sia localizzato solo e soltanto nel servizio di interesse e non richieda l’intervento di più teams.
- Principio SRP (Single Responsibility Principle): rispettare il principio SRP nella progettazione dei microservizi è fondamentale per evitare la creazione di “macroservizi” che riproducono la complessità monolitica.
- Database Distribuito: la necessità di gestire dati distribuiti tra diversi microservizi può richiedere l’adozione di tecniche complesse come la gestione delle transazioni distribuite e la replica dei dati.
Architettura a eventi
Si considerino due microservizi intenti a comunicare tra loro. Per fare ciò, il microservizio chiamante deve conoscere i dettagli con cui avviare la comunicazione verso il microservizio chiamato (es, le sue API, il tipo di messaggi scambiati, ecc…). Inoltre, senza un metodo di disaccoppiamento con ritenzione dei messaggi, i due microservizi devono instaurare un metodo di comunicazione sincrona.
Inevitabilmente, quindi, si crea una dipendenza funzionale fra i due microservizi.
Nell’architettura a eventi, invece, al posto di messaggi diretti che stimolano comandi e richieste di dati, vi sono soltanto eventi.
Un evento è uno statement immutabile di un fatto o di un cambiamento. Può essere visto come un segnalatore di una qualche occorrenza di interesse all’interno dell’applicazione.
L’architettura ad eventi è un pattern in cui i componenti del sistema comunicano tramite la trasmissione di eventi. Essa è composta da tre elementi chiave:
- event emitters (detti anche “producer”): componenti che generano eventi
- event consumers: componenti che elaborano eventi
- event bus: sistema di messaggistica che gestisce la distribuzione degli eventi ai consumatori.
Gli eventi sono un qualcosa di generale, noti a tutti i microservizi dell’architettura (es. click di un pulsante). Quando un MS vuole comunicare, invia un evento che il consumer deve saper gestire.
Sotto questa luce, i microservizi non sono più dipendenti.
Inoltre, adoperando un message broker come event bus, gli eventi possono essere smistati in maniera asincrona aumentando, così, il grado di scalabilità e versatilità dell’architettura.

Analisi dei flussi a real-time
Un importante utilizzo dell’architettura ad eventi è l’analisi dei flussi funzionali a real-time. Non appena gli eventi vengono prodotti è possibile tramite analisi statistica:
- analizzare la frequenza con cui gli eventi si presentano e tarare la responsività del sistema su di essi;
- non appena si verifica un determinato pattern di eventi, intraprendere azioni di correzione al fine preservare il corretto funzionamento del sistema;
Grazie a queste due importantissime prerogative, è possibile strutturare dei design pattern che permettono di ottimizzare il funzionamento architetturale in termini di performance, scalabilità e high availability.
Due di questi pattern sono event sourcing e CQRS.
Event sourcing
Molto spesso, dall’analisi dei flussi a real-time si scopre che per ottenere un determinato risultato (es. il conto delle partite correnti in ambito bancario), è necessario riesegue più volte la stessa sequenza di operazioni.
Nell’ambito di un’architettura ad eventi, invece di memorizzare lo stato di ogni singola operazione, si potrebbe pensare di memorizzare l’intera sequenza di eventi che ha condotto al risultato.
Ciò è proprio quello che fa il pattern event sourcing: invece di memorizzare direttamente lo stato corrente di un’entità, il sistema memorizza una serie di eventi che descrivono le modifiche subite da quella entità nel tempo. Per determinare lo stato corrente, è possibile rieseguire tutti gli eventi registrati dall’inizio e ricostruire lo stato.
La potenza di questo pattern è che non richiede nessun database per la memorizzazione dello stato in quanto esso permette di ottenerlo come una sequenza di operazioni in maniera programmatica.
CQRS
Il pattern CQRS (Command Query Responsibility Segregation) è un design pattern che suddivide le operazioni di lettura (query) e scrittura (comandi) in due modelli distinti.
Questo permette di ottimizzare specialmente i seguenti due aspetti del sistema in contesti ad alto carico di operazioni di lettura/scrittura.
CQRS e separazione delle operazioni di lettura e scrittura
In situazioni in cui si scrivono o si leggono frequentemente molti dati, le operazioni di lettura/scrittura possono interferire tra loro causando rallentamenti e blocchi.
Il pattern CQRS ottimizza l’accesso a un database separando le operazioni di lettura/scrittura agendo su due database distinti.
A livello programmatico, esisteranno quindi due servizi: uno preposto alla scrittura e uno preposto alla lettura.
Per analizzare il funzionamento di questo pattern, in questo scenario, è possibile identificare due casi d’uso:
- Un utente effettua un’operazione di scrittura. In tal caso viene interrogato il servizio preposto alla scrittura che scrive il nuovo record sul suo database. Subito dopo, un evento che segna la scrittura del record viene generato e spedito tramite message broker; quest’ultimo avvierà una routine che aggiornerà anche il database da cui si può solo leggere.

Figura 4 – CQRS e separazione delle operazioni di lettura/scrittura - Un utente effettua un’operazione di lettura. In tal caso si ritroverà l’ultima versione aggiornata dall’evento generato al caso 1.
In questo caso si noti che le operazioni di lettura/scrittura possono essere effettuate in maniere indipendente.
CQRS e supporto alle join distribuite
Nel contesto dei database distribuiti, le operazioni di join sono tutt’altro che banali in quanto:
- fare una join in contesti distribuiti come quello dei microservizi, significa fare richiesta ai servizi di lettura dei diversi database dei diversi MS. Bisogna poi attendere che ogni servizio risponda e mescolare i dati programmaticamente
- i vari MS potrebbero non seguire uno schema relazionale, ma potrebbero avere tipologie di database eterogenee
Il pattern CQRS può essere utilizzato per gestire join distribuite. Ogni volta che si verifica un cambiamento di dati in un microservizio, un evento sveglia un servizio il cui compito è combinare i dati programmaticamente. Questo servizio riferisce a un read-only database, il quale detiene il risultato di questa combinazione che viene anche chiamata “vista materializzata”.
In questo modo, quando viene richiesta una join, essa viene richiesta a questo servizio, il quale la legge dal suo read-only DB, senza il bisogno di dover interrogare i vari microservizi.
Conclusioni
I pattern architetturali analizzati in questo articolo rappresentano soluzioni ampiamente utilizzate nel mondo dello sviluppo software, ognuna con i propri vantaggi e sfide. L’architettura multi-tier è adatta a contesti tradizionali e relativamente stabili, mentre l’architettura a microservizi offre una maggiore flessibilità per sistemi complessi e dinamici. L’architettura a eventi, infine, è ideale per applicazioni che necessitano di elaborazione in tempo reale e di una forte disaccoppiamento tra i componenti.
La scelta del pattern giusto dipende dalle esigenze specifiche del progetto, dalle competenze del team e dagli obiettivi di scalabilità e manutenzione. Comprendere le caratteristiche di ciascun pattern e le loro implicazioni è fondamentale per sviluppare software robusto, scalabile e in grado di evolversi nel tempo.
Se anche tu vuoi far parte del team di sviluppo Nexsoft,
dai un’occhiata alle nostre opportunità di lavoro e conosciamoci subito!



