Ritorno al passato: da REST a RPC. La promessa di Cap’n Web

Il mondo del web si muove veloce, ma spesso segue traiettorie circolari. Chi sviluppa applicazioni web ha visto la logica applicativa spostarsi dal server al client e poi, progressivamente, tornare verso il server. Questo percorso non è una marcia indietro, ma una forma di maturazione.

È proprio in questo contesto che un progetto open source di Cloudflare, Cap’n Web, ha attirato la mia attenzione.

Il pendolo del web

Web 1.0: ASP, JSP, PHP. La logica risiede sul server, l’HTML viene generato server-side e il browser si limita alla visualizzazione. Un modello semplice, controllato ed efficiente nei limiti dell’epoca.

Web 2.0: prima jQuery, poi React, Angular, Vue. La logica migra verso il client e nascono le SPA (Single Page Application). L’utente riceve uno scheletro HTML, mentre il JavaScript recupera i dati via API e costruisce dinamicamente l’interfaccia. Massima flessibilità, ma con un costo: aumento delle chiamate di rete, orchestrazione spostata sul frontend o su layer BFF e una complessità crescente difficile da governare.

Oggi assistiamo a un nuovo ciclo. Next.js, Nuxt, Remix, SolidStart, TanStack Start, htmx stanno riportando la logica sul server. Non perché le SPA siano state un errore, ma perché abbiamo imparato dove ha senso far risiedere le responsabilità.

REST, GraphQL e il problema delle chiamate a cascata

Per capire il valore di Cap’n Web, vale la pena confrontare tre approcci allo scambio dati tra client e server sullo stesso caso d’uso: caricare un utente, i suoi elementi preferiti e le relative immagini.

REST è semplice e universale, ma porta con sé il problema delle chiamate in cascata (waterfall). Per ottenere N elementi con le rispettive immagini, il client deve fare 1 chiamata per l’utente, 1 per gli elementi, poi N chiamate per le immagini — N + 2 roundtrip totali. Ogni chiamata introduce latenza, e la latenza si accumula.

const user  = await fetch('/me').then(r => r.json())
const items = await fetch(`/items?lang=${user.language}`).then(r => r.json())
const media = await Promise.all(
  items.slice(0, 10).map(i => fetch(`/items/${i.id}/media`).then(r => r.json()))
)

GraphQL mitiga il problema spostando l’orchestrazione lato server tramite schema e resolver. Dal punto di vista del client, la chiamata è unica. Tuttavia, internamente, i resolver possono comunque generare waterfall se non supportati da meccanismi di batching come DataLoader. Introduce inoltre una maggiore complessità, sia nel modello che nella gestione delle query.

Batched RPC con Cap’n Web propone un paradigma diverso. Il client esprime le dipendenze tra chiamate come un grafo di Promise, e il runtime le pianifica lato server in un’unica fase. Il risultato è un singolo roundtrip, con orchestrazione centralizzata. TypeScript fornisce il controllo statico dei tipi in fase di sviluppo ma attenzione: non esiste verifica a runtime (vedi limitazioni.

const userP  = api.getUser(userId)
const itemsP = api.getItems(userP.getContext())

const pageP = itemsP.slice(0, 10).map(item => ({
  item,
  media: api.getItemMedia(item.id),
}))

const page = await pageP()

⚠️ Nota sul .map(): questo non è un normale Array.map. Cap’n Web usa internamente un meccanismo di record-replay: il callback viene eseguito una volta lato client con valori placeholder, le operazioni vengono registrate e poi riprodotte lato server. Il callback deve essere sincrono e non può produrre side effect significativi.

Cosa fa Cap’n Web e perché è interessante

Cap’n Web è una libreria JavaScript open source, ancora sperimentale, sviluppata da Cloudflare. È un parente spirituale di Cap’n Proto (stesso autore, Kenton Varda), ma con caratteristiche profondamente diverse: dove Cap’n Proto usa serializzazione binaria ad alte prestazioni, Cap’n Web usa JSON con un preprocessing step per gestire tipi speciali.

Priorità diverse: semplicità e portabilità sul web vs. performance massima.

Tra le caratteristiche principali:

  • Nessuno schema separato: TypeScript fornisce controllo dei tipi statico in sviluppo — ma non a runtime (vedi limitazioni)
  • Portabilità: funziona su browser, Node.js, Deno, Cloudflare Workers e altri runtime JS moderni
  • Peso ridotto: circa 10 KB (minify+gzip), zero dipendenze
  • Promise pipelining: le dipendenze tra chiamate si esprimono con sintassi async/await
  • Comunicazione bidirezionale: il server può chiamare il client, non solo viceversa
  • Modello object-capability: l’accesso a oggetti remoti è esplicito e controllato

L’idea di fondo è che il confine tra frontend e backend sia nato più per ragioni organizzative che tecnologiche. Cap’n Web esplora cosa accade quando questa separazione diventa più fluida.

Usi attuali confermati

Cap’n Web è già adottato internamente da Cloudflare nei remote bindings di Wrangler (disponibili dalla v4.37.0): permettono a un’istanza locale di wrangler dev di comunicare via RPC con servizi di produzione Workers, mantenendo i vantaggi dello sviluppo locale.

Limitazioni da conoscere prima di adottarlo

Una valutazione onesta richiede di guardare anche i lati meno brillanti.

Nessun runtime type checking. TypeScript offre solo garanzie statiche. A runtime, un client può invocare metodi con parametri del tipo sbagliato senza che il sistema lo impedisca. Per applicazioni esposte a client non fidati è necessario integrare una libreria di validazione come Zod. Cloudflare stessa lo raccomanda esplicitamente.

Gestione esplicita della memoria. Il garbage collector JavaScript non può tracciare cicli che attraversano connessioni RPC remote. Gli stub RPC devono essere disposti esplicitamente (stub.dispose() o tramite using con explicit resource management), pena memory leak sul server.

Autenticazione WebSocket non banale. Le WebSocket API nei browser non permettono di inviare header personalizzati, rendendo impossibile usare cookie o token Bearer nel modo convenzionale. Cap’n Web promuove un pattern di autenticazione in-band (un metodo RPC che restituisce l’API autenticata dopo verifica), molto interessante ma prevede un cambiamento architetturale rispetto ai pattern REST a cui molti team sono abituati.

Quando ha senso usarlo

Cap’n Web non è una soluzione universale, né ambisce a esserlo:

  • REST rimane la scelta giusta per API pubbliche, interoperabilità e contratti tra sistemi eterogenei
  • RPC è più adatto a workflow interni, dove client e server sono sviluppati e governati dallo stesso team
  • GraphQL resta competitivo dove serve flessibilità nelle query e un tooling maturo
  • Cap’n Web specificamente brilla in applicazioni real-time e collaborative, microservizi JavaScript-to-JavaScript, e scenari con modelli di sicurezza complessi basati su object-capability

La domanda chiave da porsi è: dove stanno i dati, come si muovono, chi deve orchestrarli, e chi controlla entrambi i lati della connessione?

Per concludere

Il web continua a evolvere, raramente in linea retta. Restare aggiornati non significa solo seguire le novità, ma comprenderne le motivazioni, i trade-off e i contesti di applicazione.

Cap’n Web è un progetto giovane e sperimentale, con un’adozione interna da parte di Cloudflare in strumenti specifici come Wrangler. Rispetto a tRPC e oRPC , che hanno ecosistemi più maturi, tooling consolidato e supporto WebSocket tramite subscriptions, Cap’n Web introduce concetti distinti: il modello object-capability (dove un riferimento a un oggetto remoto è anche il permesso esplicito di usarlo) e il promise pipelining full-duplex, che consentono pattern architetturali difficili da esprimere con i predecessori.
Vale la pena tenerlo d’occhio, con gli occhi aperti sulle sue limitazioni attuali.

 

*Riferimenti: Cloudflare Blog – Cap’n WebGitHub cloudflare/capnwebCloudflare Blog – Remote Bindings

Sviluppatore web da quando, per fare operazioni asincrone, si parlava con un frame nascosto e si sperava nel meglio. Oggi mi occupo di architetture IT e continuo a credere che la magia stia nel capire cosa succede dietro le quinte. Nel tempo libero pratico disc golf, ascolto musica e passo più tempo possibile con i miei cani.