Architettura software scalabile: dalla singola app ai sistemi distribuiti
Guida pratica all architettura software scalabile: da monolite a sistemi distribuiti con load balancer, CAP theorem, microservizi, sharding, cache Redis e IaC.
Quando si sviluppa un software e facile concentrarsi su ciò che l'utente vede: interfacce, funzionalità, grafica. Tuttavia, uno degli aspetti più critici di un sistema e l'architettura.
Alcune cose in un'applicazione sono facili da cambiare nel tempo:
- il colore di un bottone
- una pagina dell'interfaccia
- un layout
Altre invece sono estremamente difficili da modificare una volta che il sistema e in produzione:
- la struttura del database
- il modello di scalabilità
- l'organizzazione dei servizi
- la gestione dello stato
Per questo motivo, le decisioni architetturali iniziali hanno un impatto enorme sulla vita di un progetto.
In questo articolo vedremo come un sistema evolve: da una semplice applicazione locale fino a una piattaforma distribuita su scala globale.
Dietro una richiesta HTTP: una vera metropoli
Quando un utente interagisce con un'applicazione complessa, dietro una semplice richiesta HTTP esiste una vera e propria metropoli di servizi.
Una richiesta può attraversare diversi componenti:
- CDN
- Load Balancer
- API Gateway
- microservizi
- sistemi di cache
- database distribuiti
Ogni elemento ha una funzione precisa:
- instradare il traffico
- bilanciare il carico
- replicare i servizi
- gestire la scalabilità
Un'architettura moderna e quindi il risultato di molti livelli che collaborano tra loro.
Il punto di partenza: i requisiti
Prima di progettare qualsiasi architettura è fondamentale definire i requisiti del sistema.
Possiamo dividerli in due categorie.
Requisiti funzionali
Descrivono cosa deve fare il software.
Esempi:
- registrazione utenti
- autenticazione
- gestione degli ordini
- ricerca dei prodotti
Sono sostanzialmente la lista delle funzionalità.
Requisiti non funzionali
Descrivono come deve comportarsi il sistema.
Esempi:
- latenza massima accettabile
- comportamento sotto carico
- disponibilità del sistema
- sicurezza
- scalabilità
- manutenzione
- compliance normativa (es. GDPR)
Molti problemi architetturali nascono proprio da questi requisiti.
Il principio YAGNI: evitare la iper-ingegnerizzazione
Uno dei principi più importanti nello sviluppo software e YAGNI:
You Aren t Gonna Need It
In altre parole:
Non costruire qualcosa finché non ne hai davvero bisogno.
Molti progetti falliscono perché partono con un'architettura eccessivamente complessa.
Ad esempio:
- cluster Kubernetes
- sistemi distribuiti
- infrastrutture cloud elaborate
per applicazioni che hanno pochi utenti.
E come costruire una portaerei per attraversare un ruscello.
L'obiettivo deve essere sempre ridurre la complessità iniziale.
Partire semplici: il monolite
Per molti progetti la scelta migliore e iniziare con un'architettura semplice.
Un esempio classico e il monolite:
- un singolo server
- una sola applicazione
- un unico punto di ingresso
Ad esempio:
app.js
Un backend completo in un unica applicazione.
Questo approccio ha diversi vantaggi:
- sviluppo rapido
- debug semplice
- deployment immediato
- bassa complessità operativa
Quando il monolite non basta più
Immaginiamo un monolite come un monolocale.
Finché ci sono poche persone tutto funziona bene.
Ma se iniziamo a organizzare una festa:
- la RAM si esaurisce
- la CPU si satura
- il sistema diventa lento
In altre parole, compaiono colli di bottiglia.
E a questo punto che entra in gioco la scalabilità.
Scalabilità verticale: quando conviene
La forma più semplice di scalabilità e quella verticale.
Consiste nell'aumentare la potenza della macchina.
Ad esempio:
- più RAM
- più CPU
- dischi più veloci
Questo approccio è semplice ma ha due limiti:
- esistono limiti fisici
- i costi crescono rapidamente
Per questo motivo, oltre una certa scala si passa alla scalabilità orizzontale.
Scalabilità orizzontale: vantaggi e sfide operative
La scalabilità orizzontale consiste nell'aggiungere più server.
Da questo:
1 server
a questo:
server1
server2
server3
server4
Il traffico viene distribuito tra più macchine.
Questo introduce nuove sfide:
- gestione della rete
- sincronizzazione dei dati
- gestione dello stato
Load Balancer: come distribuisce il traffico
Quando esistono più server, serve un componente che distribuisca le richieste.
Questo componente e il Load Balancer.
Possiamo immaginarlo come un vigile urbano del traffico.
I load balancer utilizzano diversi algoritmi.
Round Robin
Distribuisce le richieste in modo ciclico.
server1
server2
server3
server1
server2
server3
Routing basato su header
Le richieste possono essere instradate in base a:
- indirizzo IP
- cookie
- header HTTP
- sessione
Sistemi distribuiti e server stateless
Quando un sistema scala orizzontalmente entra nel mondo dei sistemi distribuiti.
Un concetto fondamentale e quello di server stateless.
Significa che i server non devono mantenere lo stato dell'utente in memoria locale.
Lo stato viene salvato in sistemi condivisi come:
- database
- cache
- storage distribuiti
Questo permette di distribuire il traffico tra molti nodi senza problemi.
CAP theorem spiegato in modo pratico
Nei sistemi distribuiti esiste un principio fondamentale chiamato CAP Theorem.
Un sistema non può garantire contemporaneamente:
- Consistency
- Availability
- Partition tolerance
Bisogna sempre fare un compromesso tra questi tre elementi.
Questo è uno dei motivi per cui progettare sistemi distribuiti è complesso.
Microservizi: quando hanno davvero senso
Quando un'azienda cresce, anche i team diventano più grandi.
Immaginiamo 200 ingegneri che modificano lo stesso file.
Diventa impossibile lavorare.
Per questo motivo molte aziende adottano un'architettura a microservizi.
Ogni servizio:
- ha una responsabilità specifica
- può essere sviluppato da un team dedicato
- può usare tecnologie diverse
Ad esempio:
- team streaming -> Go
- team AI / LLM -> Python
Backend for Frontend (BFF)
Un pattern molto utilizzato nelle architetture moderne e il Backend for Frontend (BFF).
Il flusso diventa:
Client
↓
BFF
↓
Microservizi
Il BFF ha il compito di:
- aggregare le risposte dei servizi
- semplificare le API per il frontend
- ridurre il numero di chiamate
Database scalabili: sharding e replicazione dati
Quando il traffico cresce, anche il database diventa un collo di bottiglia.
Due tecniche fondamentali sono:
Sharding
Consiste nel dividere il database in più parti.
Ad esempio:
utente 1 -> DB1
utente 2 -> DB2
utente 3 -> DB3
Una funzione decide su quale database salvare i dati.
Questo permette di distribuire il carico.
Replicazione
Serve per migliorare le prestazioni in lettura.
Un database principale replica i dati su molti nodi nel mondo.
master
↓
replica1
replica2
replica3
Questo riduce la latenza per gli utenti distribuiti globalmente.
La latenza e il vero nemico
Ogni volta che una richiesta:
- attraversa la rete
- interroga più servizi
- consulta più database
la latenza aumenta.
Ridurre la latenza e uno degli obiettivi principali dell'architettura.
Cache Redis: il segreto delle applicazioni veloci
Per ridurre il carico sui database si utilizzano sistemi di cache.
Uno dei più diffusi e Redis.
La cache salva le risposte più frequenti evitando di interrogare continuamente il database.
Esiste però una famosa battuta nel mondo dell'ingegneria software:
Ci sono solo due problemi veramente difficili:
- invalidare la cache
- dare nomi alle cose
Gestire correttamente la cache e infatti tutt altro che banale.
Architettura a strati
Le architetture moderne sono costruite a strati.
Un esempio tipico:
Client
↓
CDN
↓
Load Balancer
↓
Backend
↓
Cache
↓
Database
Ogni livello ha il compito di ridurre il carico su quello successivo.
Dal data center al serverless
L'infrastruttura e evoluta molto nel tempo.
On-premise
In passato le aziende gestivano direttamente i propri server.
Cloud
Oggi gran parte delle infrastrutture gira su provider come:
- AWS
- Google Cloud
- Azure
Serverless
Nel modello serverless gli sviluppatori non gestiscono più i server.
Caricano il codice e l'infrastruttura scala automaticamente.
Infrastructure as Code
Un altro concetto fondamentale nel mondo DevOps e Infrastructure as Code (IaC).
L'infrastruttura viene definita tramite codice.
Strumenti comuni:
- Terraform
- Pulumi
- CloudFormation
Questo permette:
- versioning
- automazione
- ambienti replicabili
- deployment più sicuri
Conclusione
L'architettura di un sistema non nasce complessa.
Si evolve nel tempo.
La strategia migliore e spesso:
- partire semplici
- capire il carico reale
- scalare gradualmente
Costruire sistemi scalabili significa trovare il giusto equilibrio tra:
- semplicita
- prestazioni
- manutenibilità
E ricordare sempre una cosa fondamentale:
la complessità e il costo più alto da pagare in un sistema software.
Checklist rapida per progettare un'architettura software scalabile
Se stai disegnando o evolvendo un sistema, verifica questi punti prima di aggiungere complessità:
- requisiti funzionali e non funzionali esplicitati
- baseline di latenza, throughput e disponibilità misurata
- scelta chiara tra scala verticale e orizzontale
- stato utente esternalizzato (stateless dove possibile)
- strategia cache con regole di invalidazione definite
- piano dati per crescita (repliche, sharding, backup)
- infrastruttura versionata tramite IaC
Questa sequenza riduce errori costosi e aiuta a scalare in modo graduale e sostenibile.
Discussione