Siamo tornati alle basi dello sviluppo web?
Se si osserva la storia dello sviluppo web con distacco, si potrebbe cadere nell'errore di vederla come un cerchio che si chiude. O peggio, come una serie schizofrenica di mode passeggere dove "tutto cambia per non cambiare nulla".
Agli occhi di un analista senior, però, il movimento non è circolare, ma a spirale. Torniamo a visitare concetti che credevamo superati (il rendering server-side, i monoliti, la gestione centralizzata dello stato), ma lo facciamo ogni volta da un punto di vista più elevato, con strumenti più potenti e una consapevolezza maggiore dei compromessi in gioco.
La fatica che molti sviluppatori provano oggi – il cosiddetto JavaScript Fatigue o la sensazione di essere sopraffatti dal churn tecnologico – deriva spesso dalla mancanza di questa visione prospettica. Non si tratta di "tornare indietro" perché i nuovi tool sono difficili, ma di capire che ogni architettura è stata una risposta necessaria ai limiti della precedente, portando con sé nuovi problemi che l'iterazione successiva ha cercato di risolvere.
In questo articolo, ripercorreremo le tre grandi ere dell'architettura web, non per nostalgia, ma per analizzare lucidamente i trade-off (compromessi) di ogni fase. Solo così possiamo capire perché tecnologie come HTMX, React Server Components o Laravel Livewire non sono un "ritorno al passato", ma la sintesi dialettica necessaria per il futuro.
1. Script Transazionale
All'alba del web dinamico, l'architettura dominante era quella che Martin Fowler definisce Transaction Script. In questo paradigma, non esistevano astrazioni stratificate: ogni file sul server rappresentava un'unità logica completa che si occupava di gestire una specifica transazione dall'inizio alla fine, coordinando input, logica di business e output in un unico flusso procedurale.
Il modello mentale era semplicissimo: una richiesta HTTP mappa 1:1 con un file sul disco del server.
Arriva una GET /login.php? Il server esegue login.php.
Arriva una POST /save_user.php? Il server esegue save_user.php.
Non c'erano router, non c'erano middleware, non c'erano container per la Dependency Injection. C'era solo il codice.
Esempio di uno script
Ecco un esempio di quello che oggi chiameremmo "spaghetti code", ma che all'epoca era lo standard industriale per muovere miliardi di dollari di e-commerce:
<?php
// user_manager.php
// 1. Connessione al DB (spesso copiata in ogni file o inclusa via require)
$conn = mysql_connect("localhost", "root", "");
mysql_select_db("app_db");
// 2. Gestione della Logica di Scrittura (POST)
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'];
$email = $_POST['email'];
// Validazione immediata, "inline"
if (strlen($username) < 3) {
$error = "Nome utente troppo corto";
} else {
// Nessun ORM, SQL diretto
// Nota: all'epoca la security (escaped string) era spesso un afterthought
$sql = "INSERT INTO users (username, email) VALUES ('$username', '$email')";
mysql_query($sql);
// Redirect post-submission
header("Location: user_manager.php?success=1");
exit;
}
}
// 3. Gestione della Logica di Lettura (GET)
$users = mysql_query("SELECT * FROM users ORDER BY id DESC LIMIT 10");
?>
<!-- 4. Il Rendering (View) mescolato alla logica -->
<!DOCTYPE html>
<html>
<head><title>Gestione Utenti</title></head>
<body>
<?php if (isset($_GET['success'])): ?>
<div style="background:green; color:white">Utente salvato!</div>
<?php endif; ?>
<?php if (isset($error)): ?>
<div style="background:red; color:white"><?= $error ?></div>
<?php endif; ?>
<h1>Lista Utenti</h1>
<ul>
<?php while ($row = mysql_fetch_assoc($users)): ?>
<li>
<strong><?= $row['username'] ?></strong>
(<?= $row['email'] ?>)
</li>
<?php endwhile; ?>
</ul>
<h2>Aggiungi Utente</h2>
<form method="post">
<input type="text" name="username" placeholder="Username">
<input type="email" name="email" placeholder="Email">
<button type="submit">Salva</button>
</form>
</body>
</html>
Analisi Architetturale
Oggi guardiamo questo codice con orrore per la mancanza di separazione, la fragilità di sicurezza e la difficoltà di test. Ma fermiamoci un attimo a guardare i vantaggi oggettivi, quelli che abbiamo perso per strada.
- Locality of Behavior (LoB): Se devi capire come funziona la pagina "Gestione Utenti", devi aprire un solo file. La validazione è lì. La query è lì. L'HTML è lì. La "carico cognitivo" per capire una singola funzionalità è bassissimo. Tutto il contesto è davanti ai tuoi occhi (o almeno, nello stesso scroll verticale, anche se a volte i numeri di riga potevano raggiungere le 5 cifre).
- Latenza e semplicità: Non c'è un framework che deve fare boot, caricare 50 service provider, risolvere la reflection delle dipendenze, macinare middleware. PHP parte, esegue, muore. L'architettura Stateless e Shared Nothing permetteva una scalabilità orizzontale banale.
I giganti sulle spalle degli script
È fondamentale sfatare un mito: questa architettura non era "roba da principianti". Al contrario, è stata il motore di lancio dei più grandi colossi del web odierno, sistemi che hanno gestito un traffico inimmaginabile.
Facebook è l'esempio archetipico. Per anni, il social network più grande del mondo è stato essenzialmente una gigantesca collezione di script PHP. La filosofia "Move Fast and Break Things" di Zuckerberg era resa possibile proprio dalla Locality of Behavior. Un ingegnere poteva aprire un singolo file, modificare la logica, salvare e vedere il risultato. Non c'erano pipeline di build di 20 minuti o layer di astrazione impenetrabili. La "bruttezza" del codice era il prezzo da pagare per una velocità di iterazione che i competitor (spesso bloccati in stack Java/J2EE sovra-ingegnerizzati) non potevano eguagliare.
Anche WordPress, che alimenta oltre il 40% del web, ha dimostrato che la semplicità e l'accessibilità possono portare al successo. La sua architettura basata su hook globali e funzioni procedurali fa inorridire i puristi della programmazione ad oggetti, ma ha democratizzato l'editoria online più di ogni altra tecnologia. La sua resilienza e la facilità con cui un utente medio può incollare uno snippet in functions.php per modificare il comportamento del sito sono la prova che l'accessibilità e la semplicità battono spesso la purezza accademica.
Il crollo del modello
Perché abbiamo abbandonato questo paradiso apparente? Perché non scalava sulla complessità del dominio. Quando le regole di business diventano centinaia e le interazioni tra i dati si fanno profonde, la Locality of Behavior si trasforma in un incubo di manutenzione. Il problema non era la tecnologia in sé, ma l'incapacità di gestire la crescita senza una struttura formale. Il codice diventava "spaghetti code" non per cattiva volontà, ma per necessità: ogni nuova feature richiedeva di toccare file enormi dove logica di business, query al database e markup HTML erano fusi in un legame indissolubile.
- Duplicate Logic: Se dovevi cambiare la logica di validazione dell'email, dovevi cercarla con
CTRL+Fin 50 file diversi. - Untestable: Come fai a testare unitariamente la logica di business se è incollata a
$_POSTemysql_query? Non puoi. - Security Nightmares: Dimentica un
mysql_real_escape_stringin un solo file e sei bucato.
Serviva ordine. Serviva ingegneria.
2. Model View Controller (MVC)
Intorno al 2005-2010, il mondo PHP (e Ruby, e Python) ha iniziato a professionalizzarsi. Sono nati i "Framework Full-Stack": Symfony, Zend Framework, Ruby on Rails, Django.
La risposta al caos degli script procedurali è stata l'adozione sistematica del pattern Model-View-Controller (MVC). Man mano che le applicazioni web diventavano più ambiziose, la necessità di una struttura formale è diventata imperativa. Non si trattava più solo di far funzionare una pagina, ma di rendere il codice manutenibile da team composti da decine di sviluppatori. L'MVC ha introdotto il concetto di "Separation of Concerns" (Separazione delle Responsabilità), dividendo l'applicazione in tre componenti logiche distinte, ognuna con un perimetro d'azione ben definito.
Il Model rappresenta il cuore pulsante dell'applicazione. Non è semplicemente una rappresentazione della tabella nel database, ma il luogo in cui risiede la "verità" del business. In questa fase, abbiamo visto l'ascesa degli ORM (Object-Relational Mapping) come Hibernate in Java, Doctrine in PHP o l'ActiveRecord di Ruby on Rails. Il Model si occupa della validazione dei dati, dei calcoli complessi e della persistenza, isolando il resto del sistema dalla complessità delle query SQL e della struttura fisica del database.
La View è lo strato dedicato esclusivamente alla presentazione. Con l'avvento dei framework MVC, l'HTML ha smesso di essere generato tramite concatenazioni di stringhe disordinate, lasciando il posto ai Template Engine (come Blade, Twig, ....). Questi strumenti hanno permesso di scrivere markup pulito, arricchito da una sintassi dichiarativa per cicli e condizioni, garantendo che la logica di visualizzazione non interferisse con quella applicativa. La View riceve dati "passivi" e si limita a trasformarli nella rappresentazione visuale finale per l'utente.
Infine, il Controller funge da direttore d'orchestra e punto di ingresso per ogni interazione. Il suo compito è intercettare la richiesta HTTP, interpretare le intenzioni dell'utente e invocare le azioni necessarie sui Model. Una volta ottenuti i risultati, il Controller decide quale View renderizzare e le passa i dati necessari. Questa separazione assicura che il flusso di navigazione sia distinto sia dalla struttura dei dati che dalla loro rappresentazione, permettendo di evolvere i singoli componenti in modo indipendente.
Il costo dell'astrazione
Il codice è diventato pulito, testabile, riutilizzabile. Abbiamo introdotto concetti da Enterprise Java nel mondo dello scripting web.
-
Route (
routes/web.php): Definisce l'URL.Route::post('/users', [UserController::class, 'store']); -
Controller (
UserController.php): Coordina la richiesta e la risposta.public function store(StoreUserRequest $request, CreateUserAction $action) { $action->execute($request->validated()); return back(); } -
Form Request (
StoreUserRequest.php): Isola la logica di validazione.public function rules() { return [ 'username' => 'required|min:3', 'email' => 'required|email' ]; } -
Service/Action (
CreateUserAction.php): Contiene la logica di business pura.public function execute(array $data) { return User::create($data); } -
Model (
User.php): Rappresenta l'entità e la persistenza.class User extends Model { protected $fillable = ['username', 'email']; } -
Migration (
create_users_table.php): Definisce lo schema del database.Schema::create('users', function ($table) { $table->id(); $table->string('username'); $table->string('email'); }); -
View (
index.blade.php): Il template per l'interfaccia utente.<form action="/users" method="POST"> <input type="text" name="username"> <input type="email" name="email"> <button>Salva</button> </form>
Abbiamo distrutto la Locality of Behavior. Per capire "cosa succede quando clicco Salva", uno sviluppatore junior deve saltare attraverso 7 file e capire il ciclo di vita del framework. La complessità spaziale del codice è esplosa.
Il Problema dell'UX: Il "Round Trip"
Mentre noi ingegneri godevamo della pulizia del codice backend, gli utenti iniziavano a soffrire. In un'architettura MVC Server-Side classica (SSR), ogni interazione richiede un caricamento completo della pagina.
L'utente clicca "Aggiungi al carrello"?
- Browser invia POST.
- Server elabora.
- Server renderizza TUTTA la pagina HTML.
- Browser scarica tutto, biancheggia, e ridisegna.
Con l'esplosione del mercato degli smartphone, il modo in cui consumiamo contenuti digitali è cambiato radicalmente. Le applicazioni native, costruite appositamente per iOS e Android, hanno introdotto uno standard di interazione estremamente elevato: transizioni fluide, feedback immediati e una navigazione senza interruzioni che ha ridefinito le aspettative di ogni utente.
In questo nuovo scenario, il classico "round trip" del web tradizionale ha iniziato a mostrare tutti i suoi limiti. Vedere lo schermo che diventa bianco per una frazione di secondo a ogni clic, o attendere il ricaricamento completo dell'intera pagina per una piccola modifica, è diventata un'esperienza percepita come lenta e "a scatti". Il contrasto tra la reattività delle app installate e la macchinosità del browser era ormai diventato inaccettabile.
3. Single Page Applications (SPA)
La risposta dell'industria, tra il 2012 e il 2020, è stata drastica: abbandonare il rendering HTML lato server a favore di una logica interamente gestita dal client. Il browser smette di essere un semplice visualizzatore di documenti e diventa l'ambiente di esecuzione di una vera e propria applicazione che consuma dati grezzi. Questo cambio di paradigma ha permesso di colmare il gap prestazionale con le app native, offrendo interfacce reattive e navigazioni istantanee.
Nasce l'era delle SPA (React, Angular, Vue). L'architettura si spezza in due mondi distinti connessi da un filo sottile:
- Frontend (Il Client): Un'applicazione JavaScript completa che gira nel browser. Gestisce il routing, la validazione, il rendering, lo stato.
- Backend (L'API): Un server "muto" che parla solo JSON. Stateless, RESTful (o GraphQL).
L'esplosione della complessità accidentale
All'inizio sembrava grandioso. Separazione totale! Frontendisti e Backendisti potevano lavorare in parallelo. Ma ben presto, ci siamo resi conto che avevamo creato un mostro di complessità.
1. La moltiplicazione degli stati
Prima, lo stato era nel Database. Punto. Con le SPA, abbiamo lo stato nel DB, lo stato in transito nella risposta API, lo stato nello Store Redux/Pinia del client, e lo stato nel DOM locale. Sincronizzare questi stati è diventato il problema più difficile dello sviluppo frontend moderno. (Cache invalidation anyone?)
2. I limiti del JSON
Il backend manda dati grezzi ({"id": 1, "name": "Mario"}) al client che deve sapere come renderizzarli. Questo crea High Coupling (Accoppiamento Forte), cioè se cambi il nome di un campo nel DB, devi aggiornare:
- Il Model Backend
- La Resource API
- L'Interfaccia TypeScript nel Frontend
- Il Componente React
3. Il problema del bundle size e dell'hydration
Siamo arrivati al paradosso per cui, per visualizzare una semplice lista di utenti, il browser deve scaricare e processare centinaia di kilobyte di framework e logica applicativa. Questo "payload" iniziale non solo rallenta il tempo di caricamento, ma costringe il client a un lavoro computazionale enorme prima ancora di poter mostrare il contenuto reale. È l'inefficienza della distribuzione: spesso spediamo l'intera fabbrica (il framework) al cliente, invece di consegnargli semplicemente il prodotto finito (l'HTML). Il browser deve:
- Scaricare l'HTML vuoto (
<div id="app"></div>). - Scaricare il JS.
- Parsare ed eseguire il JS.
- Fare le chiamate API per i dati.
- Renderizzare il DOM.
Il risultato? Spinner di caricamento ovunque. Per mitigare questo, abbiamo inventato il Server Side Rendering (SSR) per le SPA (Next.js, Nuxt), aggiungendo un altro strato di complessità mostruosa (Hydration, Re-Hydration, doppio rendering server/client).
Avevamo ricreato i problemi del punto 1 (spaghetti code, ma in JS) con la complessità distribuita del punto 3.
4. HTML-over-the-wire
Ed eccoci all'oggi. La spirale ha compiuto un giro e gli sviluppatori esperti, stanchi di gestire state management distribuiti e pipeline di build fragili, hanno iniziato a porsi una domanda:
"E se fosse il server a mandare l'HTML, ma non tutta la pagina, solo il pezzettino che serve?"
Questa è la filosofia dietro HTMX, Hotwire (Rails), Laravel Livewire e, in modo diverso ma parallelo, i React Server Components (RSC).
Il cambio di paradigma
L'idea è semplice ma rivoluzionaria rispetto al decennio SPA: Il server è la Single Source of Truth.
Invece di:
Click -> JS intercetta -> Fetch JSON -> JS aggiorna Stato -> JS ricalcola VDOM -> DOM Update
Abbiamo:
Click -> Attributo HTML intercetta -> Fetch HTML parziale -> Swap nel DOM
HTMX
Immaginiamo la stessa funzionalità di "Salvataggio Utente" del punto 1, ma modernizzata utilizzando HTMX.
<!-- index.html -->
<!-- La lista si aggiorna automaticamente ascoltando l'evento 'newUser' -->
<ul id="user-list" hx-get="/users/fragment" hx-trigger="newUser from:body">
@include('partials.user-list')
</ul>
<!-- Il form manda i dati via AJAX e, se ha successo, emette un segnale -->
<form hx-post="/users" hx-swap="none">
<input type="text" name="username">
<button type="submit">Salva</button>
</form>
// UserController.php
public function store(Request $request) {
// 1. Validazione e DB (tutta la potenza di Laravel/Symfony)
$user = User::create($request->validate([...]));
// 2. Invece di JSON, o redirect, mandiamo un header speciale
// che dice al frontend: "Ehi, è successo qualcosa!"
return response()->noContent()->withHeaders([
'HX-Trigger' => 'newUser'
]);
}
Perché questa è la sintesi vincente?
Questo approccio permette di recuperare i vantaggi dello sviluppo server-side tradizionale senza rinunciare alla fluidità e alla reattività tipiche delle Single Page Applications moderne. Ecco i motivi principali per cui questa architettura rappresenta una sintesi estremamente efficace:
- Locality of Behavior Ritrovata: Guardando l'HTML, capisco esattamente cosa fa il bottone (
hx-post="/users"). Non devo cercare la funzionehandleSubmitin un file JS separato. - Zero State Management Client-Side: Non c'è uno store. Lo stato è nel database. L'HTML è sempre un riflesso fedele dello stato del server.
- Payload Ridotto: Non scarico 2MB di React/Angular. Scarico solo piccoli frammenti di HTML quando servono.
- UX "SPA-Like": L'utente non vede refresh. Percepisce fluidità.
5. Scegliere la complessità giusta
Non esiste "l'architettura perfetta". Esiste solo l'architettura adatta al problema che stai risolvendo.
La lezione di questi 20 anni è che la Place-Oriented Architecture (come la chiamano i creatori di HTMX) o la Locality of Behavior sono valori che abbiamo sacrificato troppo in fretta sull'altare della separazione a tutti i costi.
Come Tech Lead, il tuo compito non è seguire l'hype dei micro-frontend o delle architetture "Edge-first". Il tuo compito è valutare il Budget di Complessità.
- Stai costruendo un tool di editing video nel browser? O Google Maps? Usa una SPA. Ti serve gestione di stato complessa sul client.
- Stai costruendo un gestionale, un e-commerce, un dashboard B2B, un blog, un social network? (Ovvero il 90% del web). Considera seriamente HTML-over-the-wire.
Recuperare la semplicità non è un passo indietro. È il segno definitivo di maturità. Significa aver capito che il codice migliore è quello che non devi scrivere (o manutenere).
Siamo tornati alle basi, ma con i superpoteri.