Salta al contenuto principale
Dal blog

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.

Foto profilo di Alessandro IannaconeAlessandro Iannacone

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:

  1. partire semplici
  2. capire il carico reale
  3. 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à:

  1. requisiti funzionali e non funzionali esplicitati
  2. baseline di latenza, throughput e disponibilità misurata
  3. scelta chiara tra scala verticale e orizzontale
  4. stato utente esternalizzato (stateless dove possibile)
  5. strategia cache con regole di invalidazione definite
  6. piano dati per crescita (repliche, sharding, backup)
  7. infrastruttura versionata tramite IaC

Questa sequenza riduce errori costosi e aiuta a scalare in modo graduale e sostenibile.

Discussione