Tecnologie per lo sviluppo di applicazioni di rete server-side: Javascript, Node.js e NestJS - Unina
←
→
Trascrizione del contenuto della pagina
Se il tuo browser non visualizza correttamente la pagina, ti preghiamo di leggere il contenuto della pagina quaggiù
Scuola Politecnica e delle Scienze di Base Corso di Laurea in Ingegneria Informatica Tesi di Laurea Triennale in Ingegneria Informatica Tecnologie per lo sviluppo di applicazioni di rete server-side: Javascript, Node.js e NestJS Anno Accademico 2019/2020 relatore Ch.ma prof. Valeria Vittorini candidato Dario Guarracino matr. N46002963
Indice 1 Architetture per lo sviluppo web 1 1.1 Architettura Client - Server . . . . . . . . . . . . . . . . . . . . . . 1 1.2 Architettura multi-tier e Web Server . . . . . . . . . . . . . . . . . 2 1.2.1 Model View Controller . . . . . . . . . . . . . . . . . . . . . 3 1.3 Sviluppo di un Web Server . . . . . . . . . . . . . . . . . . . . . . . 4 2 Linguaggi di programmazione e sviluppo client-side 5 2.1 Panoramica e stato dell’arte . . . . . . . . . . . . . . . . . . . . . . 5 2.2 Javascript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.2.1 JS e la manipolazione del DOM . . . . . . . . . . . . . . . . 8 2.2.2 Single Page Applications . . . . . . . . . . . . . . . . . . . . 10 3 Node.js e lo sviluppo server-side 14 3.1 V8 e l’approccio event-driven . . . . . . . . . . . . . . . . . . . . . 14 3.1.1 Blocking e non-blocking I/O . . . . . . . . . . . . . . . . . . 14 3.1.2 La soluzione di Node.js e l’approccio event-driven . . . . . . 16 3.2 Web Framework e REST . . . . . . . . . . . . . . . . . . . . . . . . 20 3.2.1 Framework e principio DRY . . . . . . . . . . . . . . . . . . 20 3.2.2 Web Framework per le Classical Web Applications . . . . . . 21 3.2.3 Approccio RESTful e il concetto di Risorsa [6] . . . . . . . . 22 3.3 Node.js e Express.js . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 3.3.1 Oltre Express.js . . . . . . . . . . . . . . . . . . . . . . . . . 26 i
INDICE 4 NestJS - Princìpi ed applicazioni 28 4.1 L’architettura a moduli e le dipendenze . . . . . . . . . . . . . . . . 28 4.2 Caso di studio e implementazione di un Web Server RESTful . . . . 32 4.2.1 Caso di studio . . . . . . . . . . . . . . . . . . . . . . . . . . 32 4.2.2 Interfaccia REST . . . . . . . . . . . . . . . . . . . . . . . . 33 4.2.3 Autenticazione e Autorizzazione . . . . . . . . . . . . . . . . 34 4.2.4 Approccio Controller - Service . . . . . . . . . . . . . . . . . 35 Appendici 36 A Implementazione Web Server RESTful in NestJS 37 ii
A Mamma e Papà, ai miei fratelli e a Jolie. La mia forza e il mio orgoglio.
Capitolo 1 Architetture per lo sviluppo web Il 6 Agosto 1991, nei pressi del CERN di Ginevra, Tom Berners-Lee pubblicava e rendeva al mondo e alla storia il primo sito Web, con poco più 10 righe di testo. Pochi mesi prima scriveva httpd, il primo server per il Word Wide Web e la prima versione di HTML, il linguaggio di formattazione de-facto per lo sviluppo di interfacce web. Mentre da un lato crescevano - e crescono indefinitamente tutt’oggi – le tecnologie per lo sviluppo client-side, dall’altro si è costantemente pensato ad architetture, linguaggi e strategie di sviluppo per la parte server-side di applicazioni web (web application o web-based app in inglese), il cosiddetto Back-end.[2] 1.1 Architettura Client - Server L’architettura Client – Server è un’architettura di rete ove le parti comunicanti sono due o più. Nella sua forma più basilare, il server è un host sempre attivo, che risponde alle richieste di servizio di molti client. Nel caso di applicazioni web, un web server, sempre attivo e pronto a ricevere, risponde alle richieste dei browser, attivi sui client. La richiesta può trattarsi di una risorsa qualsiasi: un file di testo, html, un’immagine, file audio, testo formattato o altro che possa servire al 1
CAPITOLO 1. ARCHITETTURE PER LO SVILUPPO WEB client per completare il suo servizio. [8] Nel caso di servizi che devono gestire un numero molto grande di richieste, la soluzione oggi più utilizzata è quella dello scalamento orizzontale, cioè allocare più server di potenza sostenuta in modo che funzionino come un’unica entità logica. 1.2 Architettura multi-tier e Web Server Nell’ambito web, un Web Server è la parte software in esecuzione sul server e gestisce le richieste di pagine web inviata dal client attraverso il protocollo http (o con la versione sicura https) . Facendo riferimento all’architettura multi-tier, un applicazione web è suddivisa: • Logica di presentazione: È il livello più alto: mostra all’utente le operazioni che può eseguire e fornisce in output informazioni che l’utente può capire. Nelle web application si trova sul browser web del client (front-end) • Livello applicazione: Si occupa delle logiche di business di un’applicazio- ne, eseguendo operazioni dettagliate e prendendo in input eventuali dati dal livello di presentazione. Comunica direttamente con il livello dati per la persistenza e la gestione degli stessi. In una web application, il livello appli- cazione riguarda il Web Server ed è il livello che genera contenuti dinamici da servire al livello di presentazione • Livello dati: È costituito da un database che si occupa della gestione e della persistenza dei dati e presenta un software che fornisce al livello applicazione l’accesso agli stessi. Generalmente, il back-end viene visto come l’insieme del livello applicazione e il livello dati. Tra le più famose applicazioni software che implementano web server troviamo: 2
CAPITOLO 1. ARCHITETTURE PER LO SVILUPPO WEB • Apache HTTP Server e Apache Tomcat, open source, sviluppati da Apache Software Foundation • nginx, open source, rilasciato su licenza BSD-Like • ColdFusion, distribuita da Adobe Figura 1.1: multi-tier architecture 1.2.1 Model View Controller Introdotto originariamente dal linguaggio SmallTalk, è un pattern architettu- rale e si basa sul principio della separazione delle responsabilità (SoC - Separation of Concerns in inglese) fra i suoi componenti: • Model: è il responsabile della gestione dei dati, della logica e delle regole applicative; esso definisce la struttura dati dinamica dell’applicazione. • View: la rappresentazione visiva del Model (o di più Model), quando si parla di web framework, si riferisce al contenuto HTML (CSS e JS) che mostra l’informazione all’utente. • Controller: è il responsabile della logica di business; riceve i dati di input all’utente e manipola il model, per poi rispondere a sua volta attraverso le View. 3
CAPITOLO 1. ARCHITETTURE PER LO SVILUPPO WEB Figura 1.2: Diagramma delle interazioni nel MVC Il Model View Controller oggi è un pattern standard adottato dalla maggior parte dei framework back-end, ma si estende a qualsiasi tipo di applicativo, e le interazioni tra i suoi componenti dipendono fortemente dalle tecnologie e dai linguaggio di programmazione con cui si opera. 1.3 Sviluppo di un Web Server Il problema che ci poniamo in questo elaborato è lo studio del linguaggio Java- Script e le sue applicazioni client-side, per poi proseguire con l’analisi di sviluppo di un web server leggero con l’utilizzo di Node.js, un ambiente di runtime Java- Script che permette di costruire applicazioni server-side. Studieremo le principali differenze tra Node.js e i web server quali Apache e nginx, lo sviluppo attraverso il linguaggio Javascript e Typescript e l’utilizzo di frameworks come Express.js e NestJS. Concluderemo poi con un’analisi sullo sviluppo di un’interfaccia API con GraphQL e le principali differenze con l’approccio REST. 4
Capitolo 2 Linguaggi di programmazione e sviluppo client-side 2.1 Panoramica e stato dell’arte Lo sviluppo di un software include ovviamente la scelta di uno o più linguag- gi di programmazione da utilizzare. Nell’ambito dei web server, i linguaggi di programmazioni che hanno avuto maggior rilievo negli ultimi anni sono: • PHP : (acronimo ricorsivo di « PHP : Hypertext Prepocessor), linguaggio di scripting interpetato, attualmente quello più utilizzato nel panorama web, sia per la semplicità di utilizzo che per l’uso che ne fanno i Content Management Software (CMS) come WordPress, Magento, Shopify. . . • Java : linguaggio de-facto per la programmazione orientata agli oggetti e l’utilizzo di design patterns specifici. Particolare importanza hanno i framework utili allo sviluppo web come Spring e Hibernate. • Python : anch’esso un linguaggio di scripting interpretato e orientato agli oggetti ma in generale multi-paradigma, molto usato anche in ambito di calcolo numerico. 5
CAPITOLO 2. LINGUAGGI DI PROGRAMMAZIONE E SVILUPPO CLIENT-SIDE • .NET : più che un linguaggio, è una piattaforma general purpose, che tra i tanti strumenti include ASP.NET, insieme di tecnologie per lo sviluppo di servizi Web attraverso i linguaggio di programmazione che supportano il .NET Framework, come C# e Visual Basic .NET Figura 2.1: Percentuale dei siti web che usano un linguaggio di programmazione server-side, aggiornato al 13 Febbrario 2019 I linguaggi sopracitati, oltre a fornire strumenti per lo sviluppo di altro genere di software, formano in larga parte ciò che ha costituito il Web 2.0, e cioè un approccio al web più dinamico: la creazione e l’editing di contenuti attraverso i Content Management Software, l’E-Commerce, il Blogging e i Social Network, hanno trasformato la funzione del web da semplice strumento di consultazione a piattaforma dove poter contribuire popolando lo stesso con i propri contenuti. [10] A contribuire allo sviluppo del Web dinamico è sicuramente stata la tecnica AJAX (Asynchronous JavaScript and XML), che ha consentito alle pagine web, prima statiche, la comunicazione fra web browser e server attraverso il lin- guaggio di scripting JavaScript. La rivoluzione di questa tecnologia è stata per l’appunto l’Asynchronous, cioè il caricamento asincrono dei dati dal server senza l’interruzione del flow della pagina corrente. Ciò ha in primis ottimizzato l’uti- lizzo della banda, evitando il ricaricamento superfluo del contenuto HTML delle 6
CAPITOLO 2. LINGUAGGI DI PROGRAMMAZIONE E SVILUPPO CLIENT-SIDE pagine ad ogni interazione e, di conseguenza, migliorato di gran lunga l’esperienza dell’utente. Figura 2.2: Schema di funzionamento di una richiesta AJAX 2.2 Javascript Inizialmente sviluppato da Brendan Eich della Netscape Communications con il nome di Mochan/LiveScript e successivamente rinominato JavaScript, è stato standardizzato nel 1997 dalla ECMA con il nome di ECMAScript. JavaScript (talvolta abbreviato in JS) - che non ha nessuna relazione con Java se non per le sintassi più o meno simili - è un linguaggio di scripting a tipizzazione debole orientato agli oggetti e agli eventi, dove l’uso di questi ultimi è particolarmente frequente nello sviluppo web client-side. 7
CAPITOLO 2. LINGUAGGI DI PROGRAMMAZIONE E SVILUPPO CLIENT-SIDE Figura 2.3: Hello World in JavaScript e HTML 2.2.1 JS e la manipolazione del DOM È nella logica di presentazione che infatti JavaScript trova il suo maggior uso: inserito nei file HTML delle pagine web, consente di accedere e modificare il DOM (Document Object Model). Il DOM è un modello strutturale a oggetti, una forma di rappresentazione dei documenti di cui anche i browser si servono co- me interfaccia per l’accesso agli elementi della pagina. La pagina è rappresentata come un albero e ogni ramo porta a un nodo, ogni nodo contiene degli oggetti. JavaScript, attraverso l’interfaccia window, supporta l’accesso diretto al DOM, permettendo così la sua manipolazione per costruire interfacce grafiche. Partico- lare successo ha avuto jQuery, una libreria open-source JavaScript che facilita la gestione degli eventi, la manipolazione del DOM e le animazioni degli elemen- ti con un approccio modulare, offrendo una grande estensibilità da parte della community. Al 2018, jQuery è la libreria JavaScript più utilizzata sul Web [9]. Tuttavia, con le recenti versioni di JavaScript e dei browser, le funzionalità che rendevano jQuery un must per gli sviluppatori, sono diventate funzionalità 8
CAPITOLO 2. LINGUAGGI DI PROGRAMMAZIONE E SVILUPPO CLIENT-SIDE Figura 2.4: Percentuale di siti che usano varie librerie JavaScript. Aggiornato al 3 Ottobre 2019 9
CAPITOLO 2. LINGUAGGI DI PROGRAMMAZIONE E SVILUPPO CLIENT-SIDE standard del linguaggio, rendendo quindi la libreria uno strumento da cui poter prescindere. 2.2.2 Single Page Applications Nell’Aprile 2002, Stuart Morris scrisse il primo sito self-contained su slash- dotslash.com, compatibile esclusivamente con Internet Explorer e utilizzando le tecnologie di quel tempo: niente AJAX, niente frameworks, solo DHTML, un’in- sieme di tecnologie che permettevano di cambiare dinamicamente il contenuto delle pagine attraverso direttive JavaScript e CSS. Dimostrò cosa si poteva fare con le tecnologie di allora e mostrò un primo esempio di Single Page Application [3]. Le SPA sono web-application (o siti web) che non hanno bisogno di essere ricaricate: l’utente interagisce con la pagina, che si aggiorna dinamicamente anche durante la navigazione inter-sito, caricando eventuali dati dal server. La web-application diventà così più simile a una Desktop-App o una Mobile-App, con il vantaggio di ottimizzare la banda usata e migliorare l’esperienza utente. Javascript Frameworks per le SPA Numerosi framework JavaScript hanno adottato i principi SPA, tra i più utilizzati troviamo: • AngularJS: il più usato tra i framework front-end; è stato sviluppato da Google e si basa sul pattern MVC e MVVM (Model View-View Model ) e sul principio di Bidirectional UI Data Binding: ogni qual volta il model viene modificato, la view associata si aggiorna e viceversa. È scritto e può essere usato con TypeScript utilizzando, per le view, un HTML esteso con direttive riservate che poi verrà compilato in HTML puro. Più avanti torneremo sulla struttura di Angular e su Typescript quando parleremo dei framework server-side. 10
CAPITOLO 2. LINGUAGGI DI PROGRAMMAZIONE E SVILUPPO CLIENT-SIDE • EmberJS: basato completamente sul MVC ; anch’esso implementa il Bi- directional Data Binding e una semplice gestione dei template attaverso Handlebars.js. • React: il più recente tra questi. Più che un framework è una libreria per costruire interfacce utente. Usa un linguaggio misto tra HTML e JS, JSX e offre un potente strumento per creare ed estendere componenti grafici. Usato con librerie di store management come Redux o MobX, React permette lo sviluppo di applicazioni complesse. È inoltre il motore di React Native, il framework per costruire applicazioni mobile native per iOS e Android. 11
CAPITOLO 2. LINGUAGGI DI PROGRAMMAZIONE E SVILUPPO CLIENT-SIDE (a) Fetching dei dati in React (b) Implementazione di una tabella in React Con l’affermazione dei framework e delle librerie client-side, che garantiscono un maggiore controllo sul codice, un’architettura più solida e strumenti di testing 12
CAPITOLO 2. LINGUAGGI DI PROGRAMMAZIONE E SVILUPPO CLIENT-SIDE più efficienti, unita anche alla crescita della velocità dei calcolatori e dei browser nel processare gli script, lo sviluppo web si sta via via orientando verso una più netta separazione tra front-end e back-end. Dapprima (e in larga parte tutt’ora), il back-end si occupava di tutta la logica di business, compreso il rendering delle pagine HTML che restituiva nella risposta HTTP. Questo tipo di architettura è detta Thick server architecture, in cui tutto il peso delle operazioni grava sul server, che ricostruisce lo stato del client a partire dalla sua richiesta (Stateless) o salvandolo stesso in memoria (Stateful). Con le Single Page Applications invece, l’archiettura back-end diventa di ti- po Thin server architecture: tutta la logica applicativa passa al client, che si occupa di aggiornare l’interfaccia e ricavare esclusivamente i dati formattati dal server. Il server quindi diventa una vera a propria API o Web Service, attra- verso la quale si può accedere alle risorse e manipolarle a seconda dei permessi dell’utente. Si riduce così di gran lunga il carico di lavoro sul server, permettendo così una completa separazione tra i due fronti a patto di stabilire un contratto di comunicazione (REST, GraphQL...) e facilitando quello scalamanto orizzontale di cui abbiamo parlato inizialmente. 13
Capitolo 3 Node.js e lo sviluppo server-side 3.1 V8 e l’approccio event-driven Node.js è un run-time system di JavaScript, open source, multipiattaforma e orientato agli eventi, costruito sul motore JavaScript V8 di Chrome. Un run-time system è un software che fornisce i servizi necessari all’esecuzione di un programma. V8 è un motore open-source sviluppato da Google, scritto in C++ che interpreta codice JavaScript e WebAssembly. È il principale motore di scripting di Google Chrome ed è stato adottato da Ryan Dahl, creatore di Node.js, per portare JavaScript lato server. La scelta di adottare V8 come motore per Node.js è stata quella di usare un linguaggio già ben conosciuto per il suo sviluppo client-side e che si adattasse per costruzione all’approccio event-driven 3.1.1 Blocking e non-blocking I/O Una system call (chiamata di sistema) è una richiesta, da parte di un processo a livello utente a un servizio di livello kernel. Una system call può riguardare il controllo dei processi (wait(), signal(), execute(), fork(), ..., la gestione del file system (read(), open(), write(), ...) o il networking(accept(), listen(), socket(), 14
CAPITOLO 3. NODE.JS E LO SVILUPPO SERVER-SIDE ... . Una system call I/O è una chiamata di sistema che implica la comunicazione con dispositivi esterni (scheda di rete, terminale, hard-drive) e generalmente i linguaggi di programmazione le implementano come bloccanti (blocking): quando un processo chiama una system call I/O di livello kernel, questi si occuperà di contattare il dispositivo ed eseguire le operazioni necessarie di lettura/scrittura. In questo lasso di tempo (di solito nell’ordine dei microsecondi, nel caso di dispositivi di rete anche millisecondi), il programma chiamante si mette in attesa della risposta del kernel, che a sua volta aspetta la risposta il dispositivo. Approccio multi-threading e multi-processo In molte applicazioni, generalmente quelle che sfruttano processi CPU-bound, questa attesa non è un problema perché il programma non deve svolgere altre operazioni se non aspettare che l’I/O sia concluso. Ma in altre applicazioni, come nello sviluppo di rete (network programming) in cui ci sono numerosi client che attendono di essere serviti, il programma potrebbe voler sfruttare quel tempo di attesa per elaborare gli input degli altri client. Tutto ciò si può risolvere usando un approccio multi-thread o multi-processo, in cui per ogni richiesta da parte di un client vengono allocate risorse per un thread/processo che si occupi di quella richiesta. I linguaggi che adottano questi approcci sono Java per il multi-threading e PHP/Ruby per il multi-processo e presentano infatti una programmazione sincrona (synchronous programming) , in cui le istruzioni vengono eseguite una dopo l’altra così come vengono scritte. Per quanto riguarda i web server questo è l’approccio finora maggiormente usato. Questa soluzione tuttavia inizia a presentare dei limiti di risorse quando il numero di richieste concorrenti inizia ad aumentare: il server potrebbe trovarsi saturo di memoria per l’allocazione delle risorse (nell’approccio multi-processo ciò è ancora più accentuato dal fatto che il fork dei processi richieda più memoria) e di conseguenza la capacità del numero di richieste al secondo cala drasticamente. 15
CAPITOLO 3. NODE.JS E LO SVILUPPO SERVER-SIDE Figura 3.1: System Call I/O Bloccanti (a) e System Call I/O non bloccanti (b) Non-blocking I/O Una soluzione alternativa a quella dell’uso di thread mul- tipli è quella della programmazione asincrona con chiamate di sistema non bloccanti. Una chiamata di sistema non bloccante restituisce immediatamente il controllo al chiamante. Il processo chiamante sarà poi informato (attraverso un interrupt) quando l’I/O sarà terminato. Questo permette al programma di eseguire altre istruzioni mentre l’operazione di I/O è in corso ed elaborare quindi altre richieste. [7] 3.1.2 La soluzione di Node.js e l’approccio event-driven Node.js utilizza un’architettura chiamata Single Threaded Event Loop, e si serve del meccanismo delle callback, cioè una funzione che viene passata come parametro ad un’altra funzione per poi essere chiamata in un secondo momento quando si verifica un evento. Il Single Threaded Event Loop funziona in questo modo: • Il Client invia una richiesta al Web Server. • Il Web Server Node.js gestisce internamente un Thread-pool limitato per servire le richieste dei client. • Il Web Server riceve queste richieste e le inserisce in una coda, chiamata Event Queue. 16
CAPITOLO 3. NODE.JS E LO SVILUPPO SERVER-SIDE Figura 3.2: Schema di funzionamento dell’Event Loop • Node.js si serve di un Single Thread in cui gira l’Event Loop, un loop indefinito che riceve le richieste e le processa. • L’Event Loop controlla se ci sono richieste client nella Event Queue e nel caso le processa, altrimenti resta in attesa. – Se ci sono richieste, ne prende una e inizia a processarla. – Se la richiesta non necessita di alcuna operazione I/O Bloccante, la processa completamente e invia la risposta al client. – Se la richiesta necessita di qualche operazione I/O Bloccante, come l’interazione con un Database, File System o servizi esterni allora esegue i seguenti passi: ∗ Controlla la disponibilità di thread dal Thread-pool e se c’è, prende un thread e gli assegna quella richiesta. ∗ Il thread si occupa di processare la richiesta, effettuare le opera- zioni bloccanti e inviare la risposta all’Event Loop attraverso la Callback. ∗ L’Event Loop invia la risposta al Client. Vantaggi e svantaggi Come si traduce questo approccio rispetto alle performance? Come esem- pio, consideriamo un caso in cui ogni richiesta al web server richiede 50ms per 17
CAPITOLO 3. NODE.JS E LO SVILUPPO SERVER-SIDE Figura 3.3: Esempio di codice di lettura di un file Sincrona e Asincrona essere completata, di cui 45ms sono per una chiamata I/O al database che può essere eseguita in maniera asincrona. La scelta di eseguire operazioni asincrone non bloccanti ci fa risparmiare fino a 45ms per ogni richiesta e sfruttarli per gestire altre richieste. [5]. Lo svantaggio di questo approccio sta principalmente nello gestire le operazioni che richiedono un intenso lavoro sulla CPU (manipolazione di dati, hashing, ecc.), dato che le operazioni vengono fatte in un singolo thread e ogni blocco di istruzioni mette in attesa il successivo. Ciò è stato in parte risolto con l’introduzione del multi-threading dalla versio- ne di Node.js 10.5.0 dove, come spiegato sopra, per gestire i task CPU-bound ven- gono creati dei worker thread, ma ciò è possibile solo in un ambiente multi-core in quanto Node permette l’utilizzo di un core per un solo thread. Risulta quin- di una scelta meno indicata per applicazioni che richiedono intense operazioni CPU, come l’Image Processing e Data Manipulation. Questo tipo di approccio I/O-driven, rende Node.js fortemente robusto per applicazioni che richiedono un elevata performance I/O quali: • Real-time Web apps come chat o tool di collaborazione e per browser game, che facendo uso di Web Socket, richiedono un elevata disponibilità da parte del server. • Dispositivi IoT: anche qui c’è un flusso di dati real-time e le richieste I/O 18
CAPITOLO 3. NODE.JS E LO SVILUPPO SERVER-SIDE sono molto frequenti. • API Server: Node.js risulta molto efficiente nel gestire numerose richieste I/O-bound come le operazioni al database. In particolare è molto usato con interfacce REST e GraphQL Un ulteriore punto di forza di Node.js è l’utilizzo di JavaScript come linguaggio. La sua scelta è stata dovuta principalmente al fatto che JavaScript è nativamente un linguaggio event-driven e supporta la gestione delle callback, oltre al fatto di essere un linguaggio con una grossa community alle spalle e molti anni di sviluppo. Node.js, con questa scelta, permette di implementare il paradigma "JavaScript everywhere", dove sia il front-end che il back-end sono sviluppati intorno ad un unico linguaggio. Particolarmente utilizzati sono gli stack MEAN e MERN. Il primo, (Mongo-Express-Angular-Node) è uno stack di sviluppo che include tutte le tecnologie necessaria per lo sviluppo di un’applicazione che includa una web app e un web server, entrambi scritti in JavaScript. Il secondo è una variante del primo con l’utilizzo di React invece di Angular. Inoltre, con il rilascio delle nuove versioni di EcmaScript (ES6, ES7 e da poco anche ES8), uno degli svantaggi di Node.js, il Callback Hell è stato risolto. Il Callback Hell è la situazione in cui, per via dell’approccio event-driven e dell’u- tilizzo delle callback, il codice viene innestato man mano rendendolo praticamente illegibile. Con l’introduzione delle Promise il problema è stato in parte risol- to e il codice reso più leggibile; successivamente, con l’inserimento dei costrutti Async/Await, la struttura del codice diventa totalmente simile a quella di codice sincrono, sfruttando al tempo stesso la potenza della programmazione asincrona. 19
CAPITOLO 3. NODE.JS E LO SVILUPPO SERVER-SIDE (b) Implementazione con Promise- (a) Implementazione con Callback Then-Catch (c) Implementazione con Async/Await Figura 3.4: Tre diverse implementazioni asincrone in JavaScript 3.2 Web Framework e REST 3.2.1 Framework e principio DRY Nello sviluppo di web server moderni e in generale di applicazioni web, ad oggi è molto frequente l’utilizzo di framework. Un Framework in informatica è in generale una struttura preimpostata, un’architettura logica su cui progettare un software, con a disposizione libreria di codice pre-esistenti. Il principio base di un framework è il DRY (Don’t Repeat Yourself), che indica un approccio di programmazione basato sulla non ridondanza e sul riuso del codice. Un’altro principio su cui si basano i framework e le librerie è il detto Don’t Reinvent the Wheel : se il problema da affrontare è già stato risolto con una tecnologia (in questo caso si parla di libreria di codice) sviluppata da qualcun altro in un modo 20
CAPITOLO 3. NODE.JS E LO SVILUPPO SERVER-SIDE migliore, già funzionante e testato, non perdere tempo a crearne una nuova. 3.2.2 Web Framework per le Classical Web Applications Un Web Framework dunque, è un framework progettato per supportare lo svi- luppo di applicazioni web, siti web dinamici e servizi web. Il panorama di web framework è molto vasto e la scelta di usarne uno dipende fortemente dal tipo di progetto, dalle tecnologie e dalle risorse (anche umane) a disposizione. Attualmen- te la maggior parte dei siti internet sono costruiti seguendo l’approccio classical web application, in cui il server si occupa di tutte le logiche di business e di creare il contenuto HTML della pagine da restituire al client, il cui unico valore è quello presentazionale. Una variante di questo approccio, ormai ampiamente utilizzato è l’AJAX web application, dove il client, attraverso richieste AJAX (come spiegato prima ad esempio con l’uso di jQuery) permette di rendere il con- tenuto dinamico e di interrogare il server senza il bisogno di ricaricare la pagina. Molti framework moderni sono nati seguendo questo approccio, tra i più usati troviamo: Spring e Struts (Java), Laravel, Yii2, Zend Framework (PHP), Django (Python) e molti altri altrettanto validi. Questi framework, quasi tutti fatti per seguire il pattern MVC, oltre a fornire ampie librerie utili come quelle per l’accesso al Database e al File System, auth provider per l’autenticazione via Facebook, Google, Twitter ecc., strumenti di debugging e testing, offrono anche dei template engine, cioè strumenti di templating per agevolare la crea- zione dei contenuti HTML implementando costrutti di programmazione (if, for, while, etc...) e rendendo il codice più pulito e leggero. Tuttavia, con l’avvento delle applicazioni mobile, delle Single Page Applica- tions che abbiamo discusso prima e della notevole crescita dei sistemi distribuiti (ad esempio i microservizi ), un approccio classical non è più quello ideale: la gestione delle pagine e dei contenuti, la gestione e il salvataggio delle chiavi di 21
CAPITOLO 3. NODE.JS E LO SVILUPPO SERVER-SIDE autorizzazione, la comunicazione con servizi terzi, tutto ciò può diventare una prerogativa del client. Lo sviluppo mobile e delle SPA ha messo in conto di que- sti problemi, dove il bisogno che ha un app di comunicare col server c’è quando bisogna di creare, leggere, aggiornare ed eliminare dati (CRUD), autenticarsi al sistema ed esserne autorizzati e più in generale eseguire azioni che includano il mutamento o la lettura delle informazioni che gestisce il server. L’approccio ai microservizi ha accentuato ancora di più il bisogno di rendere il server quanto più leggero possibile, migrando da un’unica entità monolite a più piccole entità, ciascuna delle quali operante ad un servizio dedicato: ancora qui, Separation of Concerns. 3.2.3 Approccio RESTful e il concetto di Risorsa [6] REST, Representational State Transfer, è uno stile architetturale per siste- mi software distribuiti e indica una serie ci principi per la progettazione di Web Service. Esso si basa sul concetto di Risorsa, un entità che è accessibile e trasfe- ribile tra server e client. Di solito è l’oggetto appartenente al dominio che stiamo trattando, dunque: • Un studente universitario • Un libro di un negozio online di libri • Un articolo di un blog • La reazione di utente Facebook ad un post Nell’interazione tra client e server, quello che viene trasferito è una rappre- sentazione dello stato interno della risorsa: la rappresentazione può essere costruita in diversi formati, tra cui i più utilizzati JSON e XML , ma anche con- tenuto HTML. Il REST si appoggia al protocollo HTTP e sfrutta i suoi metodi di richiesta per stabilire il tipo di interazione. Il metodo rappresenta un tipo di 22
CAPITOLO 3. NODE.JS E LO SVILUPPO SERVER-SIDE azione e l’URL (o in gergo, Endpoint) indica la risorsa o l’insieme di risorse su cui effettuare l’azione. I metodi supportati dal protocollo e utilizzati da REST sono: • GET: Ottiene una risorsa o un insieme di risorse • POST: Crea una risorsa • PUT: Sostituisce una risorsa esistente con una nuova • PATH: Sostituisce parti di una risorsa • DELETE: Elimina una risorsa [b!] GET students recupera una rappresentazione di un’insieme di studenti GET students/1 recupera uno studente con identificativo 1 POST students Crea una risorsa studente. DELETE students/1 Elimina lo studente con identificativo 1 Attraverso quindi una semplice richiesta HTTP, con un Endpoint, gli header necessari e un eventuale body, i client possono accedere e manipolare i dati (o un’astrazione di essi) presenti sul database con la semplice comunicazione REST con il server. Un servizio che implementa l’architettura REST si dice RESTFul Web Service ed è l’approccio maggiormente utilizzato oggi per applicazioni gestio- nali, web app e app mobile, dove la maggior parte delle azioni sui dati rientrano nella categoria CRUD (Create, Read, Update, Delete. Tra i web framework presentati prima, quasi tutti ad oggi hanno implementato le librerie necessarie per creare delle API RESTful, permettendo quindi uno sviluppo client-side orientato alle Single Page Applications. 3.3 Node.js e Express.js Node.js, come già discusso, si presenta come uno strumento per costruire servizi web ad alte prestazioni I/O e l’approccio REST è tra i più utilizzati con Node. Il 23
CAPITOLO 3. NODE.JS E LO SVILUPPO SERVER-SIDE Figura 3.5: Schema di interazione tra un client e un RESTful Web Service MVC framework più esteso e maggiormente scelto per lo sviluppo sia di web server com- pleti che di servizi web è sicuramente Express.js: il suo payoff sul sito ufficiale dice Express - Framework web veloce, non categorico e minimalista per Node.js; e sono proprio i termini non categorico e minimalista che lo caratterizzano. In parti- colare il framework non forza l’utilizzo di nessun pattern architetturale, lasciando allo sviluppatore la scelta di implementarne o meno qualcuno. Ovviamente, da grandi poteri derivano grandi responsabilità e uno sviluppatore alle prime armi potrebbe cadere in approcci anti-pattern più facilmente, per cui è sempre ne- cessaria una buona conoscenza dei pattern architetturali. Un secondo punto forte è l’utilizzo dei middleware, che rendono Express.js altamente estensibile e mo- dulabile: infatti espone proprio la funzione app.use() attraverso cui si configura Express per l’utilizzo di un middleware. Per implementare un semplice servizio RESTful con Express.js, basterà definire, per ogni Endpoint "route", il metodo HTTP che accetta e l’handler da eseguire una volta ricevuta una richiesta da un client. Express.js farà sapere all’Event Loop di Node.js che una volta ricevuta la richiesta dal client, la callback da chiamare è proprio l’handler definito per quel- l’endpoint. Nell’handler va quindi la logica di business che implementeremo per gestire la richiesta e manipolare eventuali dati. Ovviamente, se si vuole seguire un approccio al codice più pulito e modulare, si potrebbe pensare di creare un 24
CAPITOLO 3. NODE.JS E LO SVILUPPO SERVER-SIDE Controller per ogni Risorsa e delegare come handler della richiesta proprio il metodo del controller (es. controller.create() per la POST, controller.update() per la PATCH, controller.getOne() per la GET e controller.delete() per la DELETE); metodo che poi potrà fare utilizzo di un Model o di una Repository per manipo- lare il dato vero e proprio. Da notare come non è stato menzionato il componente View, poiché nell’approccio RESTful, la View non è altro che la rappresentazione del dato che viene restituito al client dal controller e che può avere diverse forme (generalmente in REST viene usato JSON come formato per lo scambio di dati). Dunque si può dire che in REST viene un po’ a cadere il concetto vero è proprio di MVC, dato che l’interazione View-Controller e View-Model non è più diretta ma muta sotto forma di richiesta e risposta HTTP. Figura 3.6: Codice di esempio di un server con Express.js Lo sviluppo con Express.js non si ferma ovviamente all’implementazione di un servizio RESTful: il framework si può usare per implementare anche un web server classico con template engine (express-handlebars, Jade, ecc.) o un microservizio con protocolli dedicati. 25
CAPITOLO 3. NODE.JS E LO SVILUPPO SERVER-SIDE Figura 3.7: Middleware pattern in Express.js 3.3.1 Oltre Express.js Il minimalismo di Express.js lo rende un framework non solo altamente dinamico, ma anche una base di partenza per poter sviluppare infrastrutture al di sopra di esso. Seguendo questo modello, sono nati altri framework per Node.js, più robusti e con un’architettura consolidata: tra questi troviamo LoopBack e NestJS. In particolare ci focalizzeremo su quest’ultimo nell’ultimo capitolo. TypeScript Formalmente, è un linguaggio di programmazione sviluppato da Microsoft, a livello è pratico è un super-set di JavaScript, ma a tipizzazione statica. Basa le sue caratteristiche sullo standard ES6, che già contiene il supporto alle classi e altri costrutti utili come l’Object Destructuring e l’Async/Await; ma ciò che rende TypeScript (abbreviato in TS) preferibile a ES6 nello sviluppo di progetti in larga scala, è appunto la tipizzazione. Il linguaggio verrà poi ricompilato in JavaScript per poter essere interpretato da un web browser o, nel caso di Node.js, da V8. In particolare TypeScript aggiunge a ES6 le seguenti funzionalità: • Firma dei metodi 26
CAPITOLO 3. NODE.JS E LO SVILUPPO SERVER-SIDE • Interfacce • Moduli • Tipi di dato opzionali • Tipo Enum e altri utili • Generics: la parte più importante, ereditata da linguaggi come Java e C#, che permette di creare componenti che funzionino per più tipi dinamicamente invece che per uno solo. Adottato ormai dalla maggior parte delle librerie e framework più utilizzati (anche lato frontend da Angular e React), TypeScript si pone l’obbietivo di essere non un sostituto, ma un sovrainsieme di ES6 (a sua volte sovrainsieme di ES5), portando la fase di sviluppo ad un livello più avanzato al pari di linguaggi come appunto Java e C. Nel prossimo capitolo studieremo il framework NestJS, il suo utilizzo con Type- Script e proveremo a creare un piccolo Web Server con RESTFul API con l’utilizzo di tecniche avanzate come gli Interceptor e le Pipe. 27
Capitolo 4 NestJS - Princìpi ed applicazioni Negli ultimi anni, grazie anche a Node.js e al paradigma JavaScript Everywhere, JavaScript è diventata la lingua franca del web, sia frontend che backend. Ciò che ha reso JavaScript (e in seguito Node.js) un linguaggio molto discusso e criticato da parte della community di sviluppatori è stata proprio l’eccessiva de- mocrazia del linguaggio, che pecca di architetture e pattern nativi da imporre allo sviluppatore al fine di produrre un software più robusto. Mentre progetti come Angular e React hanno incrementato la produttività dello sviluppatore e creato e opportunità per uno sviluppo di applicazioni veloci, testabili ed estensibili, nono- stante la presenza di potenti librerie e tool per Node e con una grossa community alle spalle, uno dei problemi fondamentali non è stato risolto: l’architettura. A questo proposito, NestJS fornisce un application architecture out-of-the-box (in gergo, preconfigurato) che consente allo sviluppatore di impostare applicazioni altamente testabili, scalabili e loosely coupled. [4]. 4.1 L’architettura a moduli e le dipendenze NestJS prende ispirazione dall’architettura modulare di Angular, proponendo quin- di un’architettura basata sui moduli: ogni modulo, configurabile dichiarativamen- 28
CAPITOLO 4. NESTJS - PRINCÌPI ED APPLICAZIONI Figura 4.1: Schema di un application graph in NestJS[4] te o imperativamente, contiene il set di funzionalità per cui è stato progettato, dividendo quindi l’applicazione in tanti moduli (tutti sotto un unico modulo, il Root module), incapsulati con le proprie responsabilità (SoC ). I moduli possono avere relazioni di dipendenza con altri moduli, dipendenza che è gestita dal framework attraverso la Dependency Injection (altro approccio ripreso da An- gular). Nest, attraverso la configurazione dei moduli e delle loro dipendenze, riesce a creare un application graph, cioè una struttura dati in cui gestisce i moduli e l’iniezione delle relative dipendenze. I Providers e l’Injectable: services, repositories e altro I provider sono un importante concetto ripreso da Angular: un provider è un modo di indicare al sistema di Dependency Injection che una classe è iniettabile (injectable). Queste classi possono essere Services, cioè oggetti (di tipo single- ton) che offrono l’accesso a metodi per la gestione dei dati o per la comunicazione con altri componenti. Spesso il service si serve di una Repository che, associata ad un Model, espone i metodi e gli attributi per manipolare il dato associato e quindi di comunicare direttamente con il database. Attraverso l’uso delle Repo- sitory, possiamo implementare infatti il Repository Pattern. Un’altro oggetto 29
CAPITOLO 4. NESTJS - PRINCÌPI ED APPLICAZIONI Figura 4.2: Relazioni di dipendenza fra i componenti[4] injectable può essere una classe Factory, che astrae la creazione di una classe associata e permette di implementare il Factory Pattern. Interceptors Una delle tecniche che ha ispirato NestJS è stata l’uso dell’Aspected Oriented Programming (AOP - Programmazione orientata agli aspetti), un paradigma basato sulla creazione di entità software, gli aspetti. Una delle peculiarità della Programmazione Orientata gli Oggetti (OOP) è la modellazione del programma come uno scambio di messaggi tra oggetti, che sono entità fra loro indipenden- ti. Questo principio garantisce la modularità del sistema software ma allo stesso tempo rende complicata l’implementazione di funzionalità che sono comuni a più oggetti. Si pensi ad un componente Logger che si deve occupare di fare il logging delle operazioni che avvengono nello scambio fra gli oggetti. Invece di implemen- tare la funzionalità di logging a tutti gli oggetti, si può pensare di implementare un aspetto, cioè una parte di codice iniettabile su tutti gli oggetti che hanno bisogno di questa funzionalità (i Controller ad esempio, che effettuano il logging del contenuto di una richiesta). In Nest questo è possibile grazie agli Interceptor, una classe iniettabile, che permette di aggiungere le seguenti logiche all’oggetto su 30
CAPITOLO 4. NESTJS - PRINCÌPI ED APPLICAZIONI Figura 4.3: Schema di una richiesta GET con l’uso degli Interceptor[4] cui si inietta, permettendo di • Estendere il comportamento di una funzione • Aggiungere funzionalità extra prima o dopo l’esecuzione di un metodo • Trasformare il risultato di una funzione • Trasformare l’eccezione di una funzione • Sostituire completamente il comportamento di una funzione sotto determi- nata condizioni (ad esempio per implementare un sistema di Cache). Se pensiamo allo sviluppo REST, un interceptor si interpone fra la richie- sta del client e l’handler che gestisce la richiesta (ad esempio il Controller) per effettuare una delle operazioni elencate sopra. Ad esempio possiamo pensare di usare un interceptor per trasformare la rappresentazione di una generica risorsa, restituita del controller, per nascondere campi e informazioni che l’utente non è autorizzato a leggere. Middlewares I Middleware sono una tecnica già presente nel framework di base Express.js, e sono funzioni che vengono chiamate prima dell’esecuzione dell’handler; essi 31
CAPITOLO 4. NESTJS - PRINCÌPI ED APPLICAZIONI vengono eseguiti a cascata attraverso la chiamata next() e possono: • Eseguire qualsiasi tipo funzione • Effettuare cambiamenti di informazioni negli oggetti richiesta e risposta • Terminare immediatamente la richiesta, ad esempio per mancata autorizza- zione. • Chiamare il prossimo middleware nello stack I middleware possono implementare meccanismi di Autenticazione e di validazione della richiesta e la loro differenza con gli interceptor è che i middleware possono operare solo prima dell’esecuzione dell’handler e non hanno modo di accedere al risultato della richiesta sebbene possano accedere all’oggeto response. Gli inter- ceptor invece, possono operare sia prima che dopo la richiesta, modificando sia il risultato che i dati in input. Pipes Sono una sottocategoria di middleware che si occupano di trasformare i dati in input di una richiesta o di convalidarli. Conviene quindi implementare un meccanismo di Validazione attraverso le pipes direttamente sul metodo che si occupa di gestire la richiesta 4.2 Caso di studio e implementazione di un Web Server RESTful 4.2.1 Caso di studio Ci proponiamo di implementare un web server che faccia da interfaccia API al sito docenti.unina.it. L’attuale sito è stato recentemente ristrutturato utilizzando 32
CAPITOLO 4. NESTJS - PRINCÌPI ED APPLICAZIONI Angular, trasformandolo quindi in una vera e proprio Single Page Application, contenuta in unico script compilato, eseguita dal browser e cachata per successivi utilizzi. In Angular è definita la struttura e lo stile dell’app; i dati invece, li prende attraverso richieste AJAX dal server unina.it. Dunque abbiamo la web app - il frontend - già pronto, cosa possiamo fare? Ci proporremo di implementare un’interfaccia API REST con Node.js e NestJS, che implementi semplicemente le seguenti azioni: • Lista dei docenti • Visualizzazione di un docente • Prenotazione di un esame 4.2.2 Interfaccia REST Possiamo da subito intuire che le tre azioni possono essere facilmente tradotte nel paradigma REST. Consideriamo il docente, la prenotazione l’esame come risorse e l’azione su di essi come verbo della richiesta, in particolare: • GET /docenti - Ottiene la lista dei docenti • GET /docenti/id - Ottiene il docente con identificativo id • POST /prenotazioni - Effettua una prenotazione su un dato esame Osserviamo come nella POST la prenotazione si riferisce sempre ad un esame ma la risorsa non è presenta nella struttura dell’endpoint. Infatti l’identificativo dell’esame è un parametro che andremo a inserire nel body della richiesta HTTP. Il nome del parametro e la sua forma sono vincoli che deve imporre l’interfaccia API attraverso una documentazione. Questa è più una scelta sintattica che seman- tica , poiché REST propone anche una struttura con sotto-risorse che si traduce in sotto-path nell’endpoint; in particolare potremmo implementare la POST in 33
CAPITOLO 4. NESTJS - PRINCÌPI ED APPLICAZIONI questo modo: POST /esami/idEsame/prenotazioni dove idEsame sarà il para- metro dell’identificativo dell’esame, che inseriremo quindi nella query dell’URL invece che nel body. Il risultato non cambia, cambia solo il modo di comunicare tra le due parti. 4.2.3 Autenticazione e Autorizzazione Dato che la prenotazione di un esame ha bisogno di un utente loggato nel sistema per identificare lo studente che effettua la prenotazione, dovremmo implementa- re un meccanismo di autenticazione e autorizzazione e potremmo pensare di usare un’autenticazione Token-Based con una JWT (JsonWebToken) e un middleware in Nest che controlli che il client includa questa token nell’header e se questa sia valida e non scaduta. Brevemente, la JWT è una token generata dal server: è un oggetto JSON che contiene informazioni riguardo la scadenza della token, il soggetto che l’ha creata e altre informazioni utili al client; il tutto viene codificato in una stringa ASCII. Al momento dell’autenticazione il ser- ver la genera e viene restituita al client, che provvede a salvarla localmente (nel window.localStorage ad esempio). Quando il client deve effettuare una richiesta autorizzata, inserirà la token nell’header Authorization. Il server provvederà a leggere questo header e a convalidate la token: essa viene decodificata e il server può leggere le informazioni circa la scadenza e l’id dell’utente. Se la token è non valida, scaduta o l’utente associato non esiste imiddleware restituirà subito un errore di 401 Unauthorized al client. Se tutto va bene, il middleware inserisce nell’oggetto request le informazioni sull’utente loggato e la richiesta procede con il successivo middleware o direttamente con l’handler. Per semplicità, nel progetto in appendice non implementeremo i metodi di Autenticazione e Autorizzazione. Supporremo cioè che il client abbia già effettuato l’accesso e tenga memorizzata la JWT nello storage e che l’AuthMiddleware abbia 34
CAPITOLO 4. NESTJS - PRINCÌPI ED APPLICAZIONI già processato la richiesta. 4.2.4 Approccio Controller - Service Dato che sono tecniche che NestJS supporta out-of-the-box, le implementeremo molto velocemente. In particolare il Controller si occuperà di ricevere la richie- sta e interrogare il Service per ottenere la lista di risorse nel caso delle GET o crearne una nuova nel caso della POST. Il service si occuperà di interrogare una eventuale Repository collegata all’interfaccia del database. Dato che sempre per semplicità non abbiamo parlato di database (la scelta più veloce e semplice è quella di usare MongoDB come database), ci fermeremo all’implementazione del service, presupponendo quindi che già ci sia un livello dati implementato con un’interfaccia Repository e dei Model definiti (i model in questo caso sono quelli relativi a Docente, Prenotazione, Esame). Questo dunque è quello che ci limiteremo a fare, tralasciando molte cose es- senziali per avere una struttura completa ma avendo utilizzando comunque molti dei concetti e tecniche studiate durante questo elaborato. Il lettore che è inte- ressato all’implementazione via codice di questo caso di studio, faccia riferimento all’Appendice A. 35
Appendices 36
Appendice A Implementazione Web Server RESTful in NestJS Requisito fondamentale per creare un progetto NestJS è avere ovviamente Node.js installato sulla proprio macchina. Per l’installazione di Node.js e NPM si segua la procedura all’indirizzo https://nodejs.org/it/. Una volta installato Node.js bisognerà installare la CLI di Nest, che permette di creare un progetto Nest direttamente da terminale. Apriamo il terminale e digitiamo e seguiamo le istruzioni a video. Figura A.1: Installazione Nest CLI e creazione nuovo progetto Una volta creato il progetto, apriamolo con un IDE che supporti TypeScript (Atom, VSCode, WebStorm...) e potremo visualizzare la struttura dei file già impostata da Nest. Particolare importanza hanno: • Il package.json, un file di metadati dove sono salvate tutte le informazioni riguardo le dipendenze di terze parti gestite da NPM. 37
APPENDICE A. IMPLEMENTAZIONE WEB SERVER RESTFUL IN NESTJS • La cartalla node_modules, gestita sempre da NPM e creata seguendo le direttive contenute nel package.json • tslint.json, tsconfig.json, file di configurazione per la compilazione di Type- Script e della convalida del codice • La cartella src, il cuore del nostro progetto dove andremo a creare i file di codice che ci serviranno Per avviare il nostro server, basterà digitare il comando sul terminale: npm run start Notiamo come Nest ci crei il root module app e un relativo app.service e app.controller. Uso dei Decorator Aprendo il file app.controller, notiamo subito la direttiva @Controller : questo è un decorator è una delle funzionalità aggiunte da TypeScript a ES6. Sono delle funzioni che possono modificare o arricchire una classe, un metodo o un argomen- to andando a modificare i cosidetti metadati [1]. Nest in particolare fa un uso fondamentale dei decorators, che permettono di modularizzare il codice e renderlo più pulito. Risulta subito chiaro allora che il decorator @Controller, importato direttamente da Nest, aggiunge le necessarie funzionalità alla classe AppControl- ler affinché sia configurabile appunto come un controller. Usando questa direttiva Nest mapperà questa classe nella lista dei Controller e saprà che il nome del controller corrisponde al nome della route: dunque se il nostro controller si chiama FooController, l’endpoint associato sarà /foo. Se vogliamo che il nome della route sia diverso dal nome del controller, basterà specificarlo tra le paren- tesi di @Controller. Nel caso dell’app.controller, dato che AppModule è il root module, il controller sarà mappato sulla route di base, cioè / Allo stesso modo funziona, sempre vedendo il file app.controller la direttiva @Get: questa indica a 38
APPENDICE A. IMPLEMENTAZIONE WEB SERVER RESTFUL IN NESTJS Nest che il metodo è l’handler della richiesta GET sull’endpoint GET /. Infat- ti, una volta avviato il server, se navighiamo all’indirizzo http://localhost:3000/ notiamo che viene visualizzato un messaggio "Hello World" che non è altro che la risposta impostata dal metodo AppController@getHello, che chiama il servizio corrispondente che restituisce la stringa. Service Injection Un’ultima cosa da osservare prima di proseguire con il nostro progetto è la fun- zione contructor di AppController: il parametro definito come private readonly appService: AppService indica a Nest che nella variabile appService va iniettata l’istanza del servizio AppService attraverso la dependency injection. Le direttiva private readonly sono chiavi riservate di TypeScript che indicano che la variabile è privata ed è di sola lettura. Il service, il controller e la loro dipendenza vengono instanziati tutti in AppModule in maniera dichiarativa. Moduli personalizzati Creiamo i nostri moduli, EsameModule e DocenteModule con i relativi controller e service e configuriamoli in maniera analoga all’AppModule. Successivamente inseriamo i due moduli nel campo imports di AppModule in modo da indicare a Nest i sotto moduli del root module e costuire così l’application graph Seguendo le stesse direttive vista prima e i concetti affrontati nel capitolo 4, il controller e il service per il Docente possono essere implementati come in figura. Navigando su http://localhost:3000/docenti si potrà vedere la rappresenta- zione della lista della risorsa docente in formato JSON (scelto di default da NestJS), che altro non è che la trasformazione dell’Array di docenti fakeDocenti che abbiamo definito nel service per simulare il dato nel database. Allo stesso, modo implementiamo PrenotazioneController e PrenotazioneSer- vice, stavolta usando il decorator @Post per indicare il metodo come POST. 39
APPENDICE A. IMPLEMENTAZIONE WEB SERVER RESTFUL IN NESTJS Analogamente a prima, effettuando una POST (si può usare il tool Postman o stesso la linea di comando curl ) e inserendo nel body il campo id_esame, il server risponderà con un oggetto prenotazione con i dati relativi all’esame prenotato, allo studente che ha prenotato e all’istante di prenotazione. Riassumendo Questa ovviamente è solo una piccola anteprima di un progetto ben più ampio, ma dimostra coma sia semplice, intuitivo e veloce fare il setup di un progetto con solide basi architetturali, utilizzando concetti come Moduli, Dependency Injection, Decorators, Service, Controller, più tutti gli altri che abbiamo discusso come Inter- ceptors, Middleware, Pipes facilmente implementabili seguendo la documentazione su docs.nestjs.com. 40
APPENDICE A. IMPLEMENTAZIONE WEB SERVER RESTFUL IN NESTJS (a) Implementazione di DocenteController (b) Implementazione di DocenteService 41 (c) Aggiunta del Controller e del Service al root module
Puoi anche leggere