Perché diavolo dovrei usare Node.js? Un tutorial caso per caso
Pubblicato: 2022-03-11introduzione
La crescente popolarità di JavaScript ha portato con sé molti cambiamenti e il volto dello sviluppo web oggi è radicalmente diverso. Le cose che possiamo fare sul web oggigiorno con JavaScript in esecuzione sul server, così come nel browser, erano difficili da immaginare solo diversi anni fa, o erano incapsulate in ambienti sandbox come Flash o Java Applets.
Prima di approfondire le soluzioni Node.js, potresti voler leggere i vantaggi dell'utilizzo di JavaScript nello stack che unifica il linguaggio e il formato dei dati (JSON), consentendoti di riutilizzare in modo ottimale le risorse degli sviluppatori. Poiché questo è più un vantaggio di JavaScript rispetto a Node.js in particolare, non ne discuteremo molto qui. Ma è un vantaggio chiave per incorporare Node nel tuo stack.
Come afferma Wikipedia: "Node.js è una raccolta in pacchetto del motore JavaScript V8 di Google, il livello di astrazione della piattaforma libuv e una libreria principale, che a sua volta è scritta principalmente in JavaScript". Oltre a ciò, vale la pena notare che Ryan Dahl, il creatore di Node.js, mirava a creare siti Web in tempo reale con funzionalità push , "ispirati da applicazioni come Gmail". In Node.js, ha fornito agli sviluppatori uno strumento per lavorare nel paradigma I/O non bloccante e guidato dagli eventi.
In una frase: Node.js brilla nelle applicazioni Web in tempo reale che utilizzano la tecnologia push su WebSocket. Cosa c'è di così rivoluzionario in questo? Ebbene, dopo oltre 20 anni di stateless-web basato sul paradigma stateless request-response, abbiamo finalmente applicazioni web con connessioni in tempo reale a due vie, in cui sia il client che il server possono avviare la comunicazione, consentendo loro di scambiare dati liberamente . Ciò è in netto contrasto con il tipico paradigma della risposta web, in cui il client avvia sempre la comunicazione. Inoltre, è tutto basato sullo stack web aperto (HTML, CSS e JS) in esecuzione sulla porta standard 80.
Si potrebbe obiettare che abbiamo avuto questo per anni sotto forma di applet Flash e Java, ma in realtà si trattava solo di ambienti sandbox che utilizzavano il Web come protocollo di trasporto da consegnare al client. Inoltre, venivano eseguiti in isolamento e spesso operavano su porte non standard, che potrebbero aver richiesto autorizzazioni aggiuntive e simili.
Con tutti i suoi vantaggi, Node.js ora gioca un ruolo fondamentale nello stack tecnologico di molte aziende di alto profilo che dipendono dai suoi vantaggi unici. La Node.js Foundation ha consolidato tutte le migliori idee sul perché le aziende dovrebbero prendere in considerazione Node.js in una breve presentazione che può essere trovata nella pagina Case Studies della Node.js Foundation.
In questa guida di Node.js, discuterò non solo come si ottengono questi vantaggi, ma anche perché potresti voler usare Node.js, e perché no , usando alcuni dei classici modelli di applicazioni web come esempi.
Come funziona?
L'idea principale di Node.js: utilizzare l'I/O non bloccante e basato su eventi per rimanere leggero ed efficiente di fronte alle applicazioni in tempo reale ad alta intensità di dati che vengono eseguite su dispositivi distribuiti.
Questo è un boccone.
Ciò che significa veramente è che Node.js non è una nuova piattaforma di punta che dominerà il mondo dello sviluppo web. Invece, è una piattaforma che soddisfa un'esigenza particolare . E capirlo è assolutamente essenziale. Sicuramente non vuoi usare Node.js per operazioni ad alta intensità di CPU; infatti, utilizzarlo per calcoli pesanti annullerà quasi tutti i suoi vantaggi. Il punto in cui Node brilla davvero è nella creazione di applicazioni di rete veloci e scalabili, poiché è in grado di gestire un numero enorme di connessioni simultanee con un throughput elevato, che equivale a un'elevata scalabilità.
Come funziona sotto il cofano è piuttosto interessante. Rispetto alle tradizionali tecniche di web-serving in cui ogni connessione (richiesta) genera un nuovo thread, occupando la RAM di sistema e alla fine raggiungendo il massimo della quantità di RAM disponibile, Node.js opera su un singolo thread, utilizzando I/ O chiama, consentendogli di supportare decine di migliaia di connessioni simultanee mantenute nel loop degli eventi.
Un rapido calcolo: supponendo che ogni thread abbia potenzialmente 2 MB di memoria di accompagnamento, l'esecuzione su un sistema con 8 GB di RAM ci porta a un massimo teorico di 4.000 connessioni simultanee (calcoli presi dall'articolo di Michael Abernethy "Just what is Node .js?", pubblicato su IBM developerWorks nel 2011; sfortunatamente, l'articolo non è più disponibile) , più il costo del cambio di contesto tra i thread. Questo è lo scenario con cui ti occupi di solito nelle tradizionali tecniche di web-serving. Evitando tutto ciò, Node.js raggiunge livelli di scalabilità di oltre 1 milione di connessioni simultanee e oltre 600.000 connessioni websocket simultanee.
C'è, ovviamente, la questione della condivisione di un singolo thread tra tutte le richieste dei client, ed è una potenziale trappola nella scrittura di applicazioni Node.js. In primo luogo, un calcolo pesante potrebbe soffocare il singolo thread di Node e causare problemi a tutti i client (ne parleremo più avanti) poiché le richieste in arrivo sarebbero bloccate fino al completamento di detto calcolo. In secondo luogo, gli sviluppatori devono fare molta attenzione a non consentire a un'eccezione di gorgogliare fino al ciclo di eventi Node.js principale (più in alto), che causerà la chiusura dell'istanza Node.js (arresto effettivo del programma).
La tecnica utilizzata per evitare che le eccezioni riemergano in superficie consiste nel passare gli errori al chiamante come parametri di callback (invece di lanciarli, come in altri ambienti). Anche se qualche eccezione non gestita riesce a emergere, sono stati sviluppati strumenti per monitorare il processo Node.js ed eseguire il ripristino necessario di un'istanza arrestata in modo anomalo (anche se probabilmente non sarai in grado di ripristinare lo stato corrente della sessione utente), il più comune è il modulo Forever, o l'utilizzo di un approccio diverso con strumenti di sistema esterni upstart e monit , o anche solo upstart.
NPM: il gestore di pacchetti di nodi
Quando si discute di Node.js, una cosa che non dovrebbe assolutamente essere omessa è il supporto integrato per la gestione dei pacchetti tramite NPM, uno strumento che viene fornito per impostazione predefinita con ogni installazione di Node.js. L'idea dei moduli NPM è abbastanza simile a quella di Ruby Gems : un insieme di componenti pubblicamente disponibili e riutilizzabili, disponibili tramite una facile installazione tramite un repository online, con gestione della versione e delle dipendenze.
È possibile trovare un elenco completo dei moduli in pacchetto sul sito Web npm o accedervi utilizzando lo strumento CLI npm che viene installato automaticamente con Node.js. L'ecosistema dei moduli è aperto a tutti e chiunque può pubblicare il proprio modulo che sarà elencato nel repository npm.
Alcuni dei moduli npm più utili oggi sono:
- express - Express.js, o semplicemente Express, un framework di sviluppo Web ispirato a Sinatra per Node.js e lo standard de facto per la maggior parte delle applicazioni Node.js oggi disponibili.
- hapi - un framework incentrato sulla configurazione molto modulare e semplice da usare per la creazione di applicazioni Web e servizi
- connect - Connect è un framework server HTTP estensibile per Node.js, che fornisce una raccolta di "plugin" ad alte prestazioni noti come middleware; funge da base di base per Express.
- socket.io e sockjs - Componente lato server dei due componenti websocket più comuni oggi disponibili.
- pug (ex Jade ) - Uno dei popolari motori di creazione di modelli, ispirato da HAML, un valore predefinito in Express.js.
- mongodb e mongojs : wrapper MongoDB per fornire l'API per i database di oggetti MongoDB in Node.js.
- redis - Libreria client Redis.
- lodash (underscore, lazy.js) - La cintura di utilità JavaScript. Underscore ha avviato il gioco, ma è stato rovesciato da una delle sue due controparti, principalmente a causa delle migliori prestazioni e dell'implementazione modulare.
- per sempre - Probabilmente l'utilità più comune per garantire che un determinato script del nodo venga eseguito continuamente. Mantiene il processo Node.js in produzione a fronte di eventuali errori imprevisti.
- bluebird - Un'implementazione Promise/A+ completa con prestazioni eccezionalmente buone
- moment - Una libreria di date JavaScript per l'analisi, la convalida, la manipolazione e la formattazione delle date.
L'elenco continua. Ci sono tonnellate di pacchetti davvero utili là fuori, disponibili per tutti (senza offesa per quelli che ho omesso qui).
Esempi di dove dovrebbe essere utilizzato Node.js
CHIACCHIERARE
La chat è l'applicazione multiutente in tempo reale più tipica. Da IRC (allora), attraverso molti protocolli proprietari e aperti in esecuzione su porte non standard, alla possibilità di implementare tutto oggi in Node.js con websocket in esecuzione sulla porta standard 80.
L'applicazione di chat è davvero l'esempio ideale per Node.js: è un'applicazione leggera, ad alto traffico, ad alta intensità di dati (ma a bassa elaborazione/calcolo) che viene eseguita su dispositivi distribuiti. È anche un ottimo caso d'uso anche per l'apprendimento, poiché è semplice, ma copre la maggior parte dei paradigmi che utilizzerai mai in una tipica applicazione Node.js.
Proviamo a descrivere come funziona.
Nell'esempio più semplice, abbiamo un'unica chatroom sul nostro sito Web in cui le persone vengono e possono scambiarsi messaggi in modo uno a molti (in realtà tutti). Ad esempio, supponiamo di avere tre persone sul sito Web tutte collegate alla nostra bacheca.
Sul lato server, abbiamo una semplice applicazione Express.js che implementa due cose:
- Un gestore
GET /
richiesta che serve la pagina web contenente sia una bacheca messaggi che un pulsante "Invia" per inizializzare il nuovo input di messaggio e - Un server websocket che ascolta i nuovi messaggi emessi dai client websocket.
Sul lato client, abbiamo una pagina HTML con un paio di gestori impostati, uno per l'evento di clic del pulsante "Invia", che raccoglie il messaggio di input e lo invia attraverso il websocket, e un altro che ascolta i nuovi messaggi in arrivo sul client websockets (vale a dire, messaggi inviati da altri utenti, che il server ora vuole che il client visualizzi).
Quando uno dei clienti pubblica un messaggio, ecco cosa succede:
- Il browser cattura il clic del pulsante "Invia" tramite un gestore JavaScript, preleva il valore dal campo di input (cioè il testo del messaggio) ed emette un messaggio websocket utilizzando il client websocket connesso al nostro server (inizializzato all'inizializzazione della pagina web).
- Il componente lato server della connessione websocket riceve il messaggio e lo inoltra a tutti gli altri client connessi utilizzando il metodo broadcast.
- Tutti i client ricevono il nuovo messaggio come messaggio push tramite un componente lato client websockets in esecuzione all'interno della pagina web. Quindi raccolgono il contenuto del messaggio e aggiornano la pagina Web sul posto aggiungendo il nuovo messaggio alla bacheca.
Questo è l'esempio più semplice. Per una soluzione più robusta, potresti utilizzare una semplice cache basata sullo store Redis. Oppure, in una soluzione ancora più avanzata, una coda di messaggi per gestire l'instradamento dei messaggi ai client e un meccanismo di consegna più robusto che può coprire perdite temporanee di connessione o archiviare messaggi per i client registrati mentre sono offline. Ma indipendentemente dai miglioramenti apportati, Node.js continuerà a funzionare secondo gli stessi principi di base: reazione agli eventi, gestione di molte connessioni simultanee e mantenimento della fluidità nell'esperienza utente.

API SU UN DB OGGETTO
Anche se Node.js brilla davvero con le applicazioni in tempo reale, è abbastanza naturale per esporre i dati dai DB degli oggetti (ad esempio MongoDB). I dati archiviati JSON consentono a Node.js di funzionare senza la mancata corrispondenza dell'impedenza e la conversione dei dati.
Ad esempio, se stai utilizzando Rails, convertiresti da JSON a modelli binari, quindi esporli di nuovo come JSON su HTTP quando i dati vengono consumati da Backbone.js, Angular.js, ecc., o anche semplicemente jQuery AJAX chiamate. Con Node.js, puoi semplicemente esporre i tuoi oggetti JSON con un'API REST che il client può consumare. Inoltre, non devi preoccuparti della conversione tra JSON e qualsiasi altra cosa quando leggi o scrivi dal tuo database (se stai usando MongoDB). In sintesi, è possibile evitare la necessità di conversioni multiple utilizzando un formato di serializzazione dei dati uniforme su client, server e database.
INGRESSI IN CODA
Se stai ricevendo una quantità elevata di dati simultanei, il tuo database può diventare un collo di bottiglia. Come illustrato sopra, Node.js può facilmente gestire le connessioni simultanee stesse. Ma poiché l'accesso al database è un'operazione di blocco (in questo caso), abbiamo dei problemi. La soluzione consiste nel riconoscere il comportamento del client prima che i dati vengano veramente scritti nel database.
Con questo approccio, il sistema mantiene la sua reattività sotto un carico pesante, il che è particolarmente utile quando il client non ha bisogno di una conferma definitiva della corretta scrittura dei dati. Esempi tipici includono: la registrazione o la scrittura di dati di tracciamento dell'utente, elaborati in batch e non utilizzati fino a un momento successivo; così come le operazioni che non devono essere riflesse istantaneamente (come l'aggiornamento di un conteggio dei "Mi piace" su Facebook) in cui l'eventuale coerenza (così spesso usata nel mondo NoSQL) è accettabile.
I dati vengono accodati attraverso una sorta di infrastruttura di memorizzazione nella cache o di accodamento dei messaggi, come RabbitMQ o ZeroMQ, e digeriti da un processo di scrittura batch di database separato o da servizi di back-end di elaborazione ad alta intensità di calcolo, scritti in una piattaforma con prestazioni migliori per tali attività. Un comportamento simile può essere implementato con altri linguaggi/framework, ma non sullo stesso hardware, con lo stesso throughput elevato e mantenuto.
In breve: con Node puoi mettere da parte le scritture del database e gestirle in un secondo momento, procedendo come se ci riuscissero.
STREAMING DATI
Nelle piattaforme web più tradizionali, le richieste e le risposte HTTP vengono trattate come eventi isolati; in effetti, sono in realtà flussi. Questa osservazione può essere utilizzata in Node.js per creare alcune interessanti funzionalità. Ad esempio, è possibile elaborare i file mentre sono ancora in fase di caricamento, poiché i dati arrivano attraverso un flusso e possiamo elaborarli online. Questo potrebbe essere fatto per la codifica audio o video in tempo reale e per il proxy tra diverse origini dati (vedere la sezione successiva).
DELEGATO
Node.js può essere facilmente impiegato come proxy lato server in cui può gestire una grande quantità di connessioni simultanee in modo non bloccante. È particolarmente utile per il proxy di servizi diversi con tempi di risposta diversi o per la raccolta di dati da più punti di origine.
Un esempio: considera un'applicazione lato server che comunica con risorse di terze parti, estrae dati da origini diverse o archivia risorse come immagini e video su servizi cloud di terze parti.
Sebbene esistano server proxy dedicati, l'utilizzo di Node potrebbe essere utile se la tua infrastruttura di proxy non esiste o se hai bisogno di una soluzione per lo sviluppo locale. Con questo, intendo dire che potresti creare un'app lato client con un server di sviluppo Node.js per risorse e richieste API di proxy/stubbing, mentre in produzione gestiresti tali interazioni con un servizio proxy dedicato (nginx, HAProxy, ecc. .).
BROKERAGE - DASHBOARD DEL COMMERCIANTE DI AZIONI
Torniamo al livello di applicazione. Un altro esempio in cui domina il software desktop, ma potrebbe essere facilmente sostituito con una soluzione web in tempo reale, è il software di trading dei broker, utilizzato per tenere traccia dei prezzi delle azioni, eseguire calcoli/analisi tecniche e creare grafici/grafici.
Il passaggio a una soluzione basata sul Web in tempo reale consentirebbe ai broker di cambiare facilmente workstation o luoghi di lavoro. Presto potremmo iniziare a vederli sulla spiaggia della Florida... oa Ibiza... oa Bali.
DASHBOARD DI MONITORAGGIO APPLICAZIONI
Un altro caso d'uso comune in cui Node-with-web-socket si adatta perfettamente: tracciare i visitatori del sito Web e visualizzare le loro interazioni in tempo reale.
Potresti raccogliere statistiche in tempo reale dal tuo utente o persino spostarlo al livello successivo introducendo interazioni mirate con i tuoi visitatori aprendo un canale di comunicazione quando raggiungono un punto specifico nella tua canalizzazione. (Se sei interessato, questa idea è già stata prodotta da CANDDi.)
Immagina come potresti migliorare la tua attività se sapessi cosa stanno facendo i tuoi visitatori in tempo reale, se potessi visualizzare le loro interazioni. Con i socket bidirezionali in tempo reale di Node.js, ora puoi.
PLANCIA DI MONITORAGGIO DEL SISTEMA
Ora, visitiamo il lato infrastrutturale delle cose. Immagina, ad esempio, un provider SaaS che desidera offrire ai propri utenti una pagina di monitoraggio del servizio, come la pagina di stato di GitHub. Con il ciclo di eventi Node.js, possiamo creare un potente dashboard basato sul Web che controlla lo stato dei servizi in modo asincrono e invia i dati ai client utilizzando i websocket.
Sia lo stato interno (intra-aziendale) che quello dei servizi pubblici possono essere riportati in tempo reale e in tempo reale utilizzando questa tecnologia. Spingi ulteriormente l'idea e prova a immaginare un Network Operations Center (NOC) che monitora le applicazioni in un operatore di telecomunicazioni, un provider di servizi di cloud/rete/hosting o qualche istituto finanziario, il tutto eseguito su uno stack web aperto supportato da Node.js e websockets invece di Java e/o applet Java.
Dove è possibile utilizzare Node.js
APPLICAZIONI WEB LATO SERVER
Node.js con Express.js può essere utilizzato anche per creare applicazioni Web classiche lato server. Tuttavia, sebbene possibile, questo paradigma richiesta-risposta in cui Node.js porterebbe in giro l'HTML renderizzato non è il caso d'uso più tipico. Ci sono argomentazioni a favore e contro questo approccio. Ecco alcuni fatti da considerare:
Professionisti:
- Se la tua applicazione non ha alcun calcolo ad alta intensità di CPU, puoi compilarla in Javascript dall'alto verso il basso, anche fino al livello del database se usi il DB di oggetti di archiviazione JSON come MongoDB. Questo facilita lo sviluppo (comprese le assunzioni) in modo significativo.
- I crawler ricevono una risposta HTML completamente renderizzata, che è molto più SEO-friendly di, ad esempio, un'applicazione a pagina singola o un'app websocket eseguita su Node.js.
Contro:
- Qualsiasi calcolo ad alta intensità di CPU bloccherà la reattività di Node.js, quindi una piattaforma con thread è un approccio migliore. In alternativa, puoi provare a ridimensionare il calcolo [*].
- L'uso di Node.js con un database relazionale è ancora piuttosto problematico (vedi sotto per maggiori dettagli). Fatti un favore e prendi qualsiasi altro ambiente come Rails, Django o ASP.Net MVC se stai cercando di eseguire operazioni relazionali.
Dove Node.js non dovrebbe essere utilizzato
APPLICAZIONE WEB LATO SERVER CON UN DB RELAZIONALE DIETRO
Confrontando Node.js con Express.js con Ruby on Rails, ad esempio, c'era una decisione netta a favore di quest'ultimo quando si trattava di accedere a database relazionali come PostgreSQL, MySQL e Microsoft SQL Server.
Gli strumenti DB relazionali per Node.js erano ancora nelle fasi iniziali. D'altra parte, Rails fornisce automaticamente la configurazione dell'accesso ai dati pronta all'uso insieme agli strumenti di supporto per le migrazioni dello schema DB e altre gemme (gioco di parole). Rails e i suoi framework peer hanno implementazioni mature e comprovate del livello di accesso ai dati di Active Record o Data Mapper.[*]
Ma le cose sono cambiate. Sequelize, TypeORM e Bookshelf hanno fatto molto per diventare soluzioni ORM mature. Potrebbe anche valere la pena dare un'occhiata a Join Monster se stai cercando di generare SQL da query GraphQL.
CALCOLO/ELABORAZIONE PESANTE LATO SERVER
Quando si tratta di calcoli pesanti, Node.js non è la migliore piattaforma in circolazione. No, sicuramente non vuoi creare un server di calcolo Fibonacci in Node.js. In generale, qualsiasi operazione ad alta intensità di CPU annulla tutti i vantaggi in termini di throughput offerti da Node con il suo modello I/O non bloccante basato su eventi perché tutte le richieste in arrivo verranno bloccate mentre il thread è occupato con il tuo numero di crunch, supponendo che tu stia provando per eseguire i tuoi calcoli nella stessa istanza di Node con cui stai rispondendo alle richieste.
Come affermato in precedenza, Node.js è a thread singolo e utilizza solo un singolo core della CPU. Quando si tratta di aggiungere la concorrenza su un server multi-core, c'è del lavoro svolto dal core team di Node sotto forma di un modulo cluster [rif: http://nodejs.org/api/cluster.html]. Puoi anche eseguire facilmente diverse istanze del server Node.js dietro un proxy inverso tramite nginx.
Con il clustering, dovresti comunque scaricare tutto il calcolo pesante sui processi in background scritti in un ambiente più appropriato per questo e farli comunicare tramite un server di coda di messaggi come RabbitMQ.
Anche se l'elaborazione in background potrebbe essere inizialmente eseguita sullo stesso server, un tale approccio ha il potenziale per una scalabilità molto elevata. Tali servizi di elaborazione in background potrebbero essere facilmente distribuiti a server di lavoro separati senza la necessità di configurare i carichi dei server Web frontali.
Ovviamente, utilizzeresti lo stesso approccio anche su altre piattaforme, ma con Node.js ottieni quel throughput di richieste/sec elevato di cui abbiamo parlato, poiché ogni richiesta è una piccola attività gestita in modo molto rapido ed efficiente.
Conclusione
Abbiamo discusso di Node.js dalla teoria alla pratica, a cominciare dai suoi obiettivi e ambizioni, per finire con i suoi punti deboli e le insidie. Quando le persone incontrano problemi con Node, si riduce quasi sempre al fatto che le operazioni di blocco sono la radice di tutti i mali: il 99% degli usi impropri di Node è una diretta conseguenza.
Ricorda: Node.js non è mai stato creato per risolvere il problema del ridimensionamento del calcolo. È stato creato per risolvere il problema del ridimensionamento dell'I/O, cosa che fa davvero bene.
Perché usare Node.js? Se il tuo caso d'uso non contiene operazioni ad alta intensità di CPU né accedi a risorse di blocco, puoi sfruttare i vantaggi di Node.js e goderti applicazioni di rete veloci e scalabili. Benvenuti nel web in tempo reale.