Vai al contenuto principaleVai al footer
ruby
|
26 ottobre 15

Ruby, NodeJs, Go: vantaggi e svantaggi per lo sviluppo backend

Nelle single-page applications c'è una distinzione piuttosto netta tra il frontend ed il backend. L'aspetto di presentazione e interazione viene completamente gestita dal frontend; mentre il backend si occupa della business logic, leggendo i dati fornitigli dal frontend, elaborandoli e restituendogli i risultati.

Lorenzo MasiniDeveloper

Il mondo dell'IT è in continua evoluzione, ogni giorno nuovi linguaggi, nuove librerie e nuovi pattern di programmazione nascono e muoiono davanti ad i nostri occhi. Il Web, in particolare, ha subito negli ultimi anni dei drastici cambiamenti. Dai semplici siti web statici si è via via sentito sempre più il bisogno di una maggiore dinamicità, responsività ed integrazione col sistema operativo.

In questo momento dell'evoluzione del Web, le single-page applications sembrano essere la risposta (o compromesso) a queste esigenze. Una caratteristica importante è che il frontend viene eseguito dal browser, lato client (fatta eccezione per la prima richiesta nelle applicazioni isomorfe) mentre il backend, completamente lato server.

What's up behind the scenes?

La componente frontend è stata analizzata piuttosto approfonditamente nella serie di post BazarJS scritti dal nostro CTO Stefano Verna.

In questo articolo vorrei invece dare una (limitata) panoramica sulle alternative da poter sfruttare per il backend, concentrandomi sugli aspetti che penso siano fondamentali:

  • Felicità dell'utente: se l'applicazione restituisce dati corretti, non crasha e va veloce, l'utente è felice.
  • Felicità del cliente: se l'utente è felice (e quindi userà l'applicazione), e i costi di sviluppo, manutenzione e funzionamento (hosting) sono contenuti, il cliente è felice.
  • Felicità del programmatore: se l'utente e il cliente sono felici, e la codebase è mantenibile, facile da modificare e migliorare, il programmatore è felice.

Da questi punti di partenza quindi, vorrei marcare i termini di paragone tra le varie soluzioni: espressività del linguaggio, ecosistema e performance.

I concorrenti

In questo articolo prenderò in considerazione:

  • Ruby (MRI), il linguaggio che correntemente usiamo in Cantiere Creativo per la totalità dei progetti server side
  • Javascript (NodeJS), framework event driven per il motore Javascript V8, ha avuto molto successo negli ultimi anni
  • Go, un linguaggio moderno sviluppato da Google, in aumento di diffusione proprio in questi ultimi mesi

Espressività del linguaggio

Con espressività, in un linguaggio di programmazione, si intende la facilità e la semplicità con cui si può scrivere un algoritmo. Alcuni linguaggi rendono più semplice esprimere alcuni algoritmi attraverso pattern specifici.

Un linguaggio più espressivo, per la soluzione di un dato problema, rende il programmatore più felice.

Ruby

Ruby è un linguaggio sintatticamente piuttosto ricco (42 keywords contro le 32 del C ad esempio), possiede varie strutture dati built-in (numeri di grandezza e precisione arbitraria, stringhe, liste, mappe e set) e una libreria standard abbastanza completa.

Una peculiarità del linguaggio è che grazie alla semplicità di essere metaprogrammato ed avere una sintassi piuttosto libera (parentesi opzionali, forme postfisse, ecc) si assiste ad una proliferazione di DSL che ne aumentano ulteriormente l'espressività (il rovescio della medaglia è il doversi ricordare tutte le nuove "keywords", introdotte dai vari DSL).

Javascript

Formalmente ECMA Script, è attualmente il linguaggio di elezione per la programmazione web lato client. A causa della sua storia, ha accumulato una serie di bug e antipattern, che però dovrebbero essere risolti nel prossimo standard del linguaggio (ECMA Script 6 che è in lavorazione). Ha una sintassi molto simile a quella del C, con un'espressività paragonabile, pur avendo strutture dati di più alto livello (stringhe, array e hash) ed essendo orientato agli oggetti (prototype-based). Esistono alcuni linguaggi (es. CoffeeScript) aventi come target Javascript, finalizzati a nascondere le varie ideosincrasie del linguaggio e offrire costrutti di più alto livello. La natura asincrona di NodeJS però penalizza un po' la leggibilità e la correttezza del codice, per il famoso problema del callback hell.

Go

È un linguaggio sintatticamente piuttosto rigido (molto simile al C), con poche keywords (25) e un set limitato di strutture dati built-in (numeri di varia grandezza e precisione, stringhe, array, slice, mappe e channels). Pur essendo così essenziale, vanta una buona espressività, soprattutto grazie alla peculiare gestione dei tipi, permettendo di sfruttare il concetto di duck typing. Rende molto semplice la programmazione concorrente e la gestione della memoria è automatica grazie al sistema di garbage collection.

Ruby logo

Ecosistema

L'ecosistema di un linguaggio consiste nei moduli aggiuntivi, nella community che lo circonda e nei tool che possono aiutare a produrre codice di qualità. Ognuno di questi aspetti è finalizzato ad aumentare la produttività:

  • la presenza di moduli aggiuntivi permette di non reinventare la ruota
  • una community attiva permette di risolvere problemi e/o perplessità più velocemente
  • i tool dedicati permettono di commettere meno errori

Del codice scritto in minor tempo — grazie a moduli aggiuntivi — e con meno bug, rende il cliente più felice.

Ruby

Pur avendo una libreria standard piuttosto variegata, ruby si avvale anche di una vasta scelta di moduli aggiuntivi, detti gemme, che permettono di risolvere una gamma piuttosto ampia di problemi. Oltre a questo, c'è una buona integrazione con i vari editor e alcuni IDE dedicati.

La community è in genere molto attiva e responsiva. La pecca maggiore contro la quale mi sono scontrato utilizzando ruby e le varie gemme è stata la scarsità (sia in termini di quantità che di qualità) di documentazione. Spesso mi è capitato di dover ricorrere alla lettura dei test o del codice per capire come utilizzare correttamente una data gemma (o addirittura delle classi della libreria standard).

Javascript

Con l'avvento di NodeJS, un framework event-driven per il motore Javascript V8, è stato possibile utilizzare questo linguaggio anche per la risoluzione di problemi al di fuori dei browser.

NodeJS espone al Javascript tutta una serie di funzioni per interagire con il filesystem, i socket, ecc. Intorno a ciò, è venuto a formarsi tutto un ecosistema di moduli aggiuntivi con un concetto simile a quello delle gemme di Ruby. Si tratta ancora di un ecosistema giovane, in rapida evoluzione e con molti aspetti da standardizzare.

Anche per quanto riguarda NodeJS la community è molto attiva.

Go

Uno dei punti di forza di Go è la presenza di una serie di tool, alcuni inclusi nella distribuzione standard, altri aggiuntivi, scritti appositamente per essere facilmente integrabili nei vari editor o IDE per rendere il codice "standard", formattandolo e dando dei suggerimenti sul coding style, ma anche eliminando automaticamente codice non raggiungibile, moduli importati non utilizzati, ecc.

Il layout del filesystem per i progetti Go è standard, così come il formato della documentazione. Anche in questo caso la community è molto attiva, e l'omogeneità conseguente all'uso di questi tool incoraggia i contributi ai progetti opensource. La documentazione è in genere soddisfacente, ma mi è capitato comunque di dover leggere test e/o sorgenti per capire il funzionamento di una data libreria.


Javascript logo

Performance

Il concetto di performance non ha di per sè molto senso, va contestualizzato. Probabilmente tutti i linguaggi hanno performance simili nell'"hello world", ma si distinguono su algoritmi più complessi e, probabilmente, ci saranno linguaggi e framework più performanti per certi tipi di task e meno per altri.

Detto questo, nel fare un paragone tra i linguaggi, oltre alla correttezza e mantenibilità del codice, è comunque un aspetto da prendere in considerazione poiché è il primo che salterà all'occhio dell'utente, discriminando una buona esperienza d'uso da una cattiva.

Un programma veloce, rende l'utente felice.

Ruby

Nella sua implementazione MRI purtroppo ruby risulta piuttosto esoso di risorse, soprattutto usando molte gemme o strategie massive di caricamento delle dipendenze (AutoLoad). A questo si aggiunge la presenza del lock globale dell'interprete, che impedisce l'esecuzione parallela di codice anche su sistemi multi-core. A questo punto, immaginandosi una applicazione ruby on rails, l'unico modo per poter gestire più richieste contemporaneamente è averne più istanze, con ulteriore utilizzo di memoria.

Javascript

Continuando con l'esempio dell'applicazione web, NodeJS è invece in grado di gestire più richieste in modo concorrente, sfruttando il fatto che, è stato implementato utilizzando il pattern reactor, completamente con chiamate non bloccanti.

Non si parla comunque di parallelismo e una singola chiamata bloccante (magari in una qualche libreria esterna) può pregiudicare il funzionamento di tutta l'applicazione bloccando tutte le richieste. Come per l'implementazione MRI di Ruby, l'unico modo per avere del parallelismo vero è caricare più istanze dell'applicazione.

Go

In Go le cose sono molto diverse. Una delle peculiarità del linguaggio è la presenza delle goroutine ovvero funzioni in grado di essere eseguite in modo concorrente rispetto alle altre. È stato reso semplice come una keyword lanciarne una. Il runtime di Go contiene uno scheduler che coordina l'esecuzione di un numero arbitrario di goroutine su un numero arbitrario di thread di sistema (modello M:N). A questo modo si ottengono dei context switch veloci pur sfruttando tutti i core della CPU. Quindi, in un'ipotetica applicazione web scritta in Go, il singolo processo è in grado di continuare a servire richieste anche se una di queste sta eseguendo un'operazione bloccante.


Golang logo

Quale scegliere?

Ruby, corredato con un framework web è perfetto per la prototipazione, permette ottenere modelli funzionanti in pochissimo tempo. Purtroppo però ha il problema della scalabilità.

NodeJS è un passo avanti rispetto al Ruby per quanto riguarda le performance, ma non possiede framework paragonabili. Inoltre c'è da considerare la facilità con cui è possibile commettere errori che pregiudichino il buon funzionamento dell'applicazione.

Go è sicuramente meno espressivo di Ruby e Javascript, un ecosistema giovane e in rapida evoluzione ma ha dei vantaggi in ambito di performance non trascurabili.

Come al solito, quando si confrontano linguaggi, non esiste una risposta assoluta alla domanda "Quale linguaggio e framework utilizzare come backend per una single-page application?" che vada bene per tutti ma, per fortuna, abbiamo a disposizione più alternative, e possiamo scegliere quella che più si adatta alle nostre esigenze.