{"id":1632,"date":"2020-08-21T11:28:47","date_gmt":"2020-08-21T09:28:47","guid":{"rendered":"https:\/\/blog.besharp.it\/?p=1632"},"modified":"2024-02-02T12:10:04","modified_gmt":"2024-02-02T11:10:04","slug":"costruiamo-un-backend-serverless-con-typescript-nodejs-e-aws-lambda","status":"publish","type":"post","link":"https:\/\/blog.besharp.it\/it\/costruiamo-un-backend-serverless-con-typescript-nodejs-e-aws-lambda\/","title":{"rendered":"Costruiamo un backend Serverless con TypeScript, Node.js e AWS Lambda."},"content":{"rendered":"\n
Su Amazon Web Services<\/strong> il servizio computazionale Serverless per eccellenza rimane AWS Lambda<\/strong>, quasi immancabile in un\u2019architettura che utilizza questo paradigma.<\/p>\n\n\n\n AWS Lambda permette di utilizzare potenza computazionale liberi dal pensiero di dover gestire i server sottostanti e senza doversi preoccupare di gestione di patch, update software o shutdown imprevisti della macchina. Utilizzando questo paradigma, possiamo concentrarci interamente sulla nostra applicazione.<\/p>\n\n\n\n AWS Lambda oggi offre numerosi runtime engine<\/strong>, oltre che la possibilit\u00e0 di creare il proprio se si vuole utilizzare un linguaggio di programmazione non ancora supportato in modalit\u00e0 managed da AWS<\/a>.<\/p>\n\n\n\n Sta quindi a noi la scelta della tecnologia con cui scrivere le AWS Lambda Functions (FaaS): possiamo utilizzare il linguaggio con cui siamo pi\u00f9 familiari<\/strong> o magari quello che ci permette di raggiungere i nostri obiettivi pi\u00f9 velocemente<\/strong>.<\/p>\n\n\n\n In questo articolo andremo a parlare di come sviluppare in modo veloce ed efficace un backend serverless utilizzando TypeScript<\/strong> come linguaggio di programmazione.<\/p>\n\n\n\n L\u2019applicazione utilizzer\u00e0 Express<\/strong>, un web application framework utile per lo sviluppo di servizi web per Node.js. Node.js<\/strong> non \u00e8 altro che il runtime environment, gi\u00e0 supportato da AWS, dove il nostro codice TypeScript, compilato in JavaScript, verr\u00e0 eseguito su Lambda.<\/p>\n\n\n\n Andremo ad analizzare la soluzione proposta per poi deployarla sul nostro account AWS. Il progetto \u00e8 consultabile e scaricabile da GitHub<\/a>! <\/p>\n\n\n\n Iniziamo col proporre lo schema architetturale<\/strong> della nostra applicazione Serverless.<\/p>\n\n\n Il codice verr\u00e0 rilasciato su una AWS Lambda function, la cui invocazione sar\u00e0 possibile solamente attraverso un API Gateway<\/strong>.<\/p>\n\n\n\n In un applicativo backend non pu\u00f2 ovviamente mancare un Data Source. Sfruttando a pieno il paradigma Serverless, andremo ad utilizzare un Aurora Serverless<\/strong> come database relazionale dove salvare e recuperare i nostri dati.<\/p>\n\n\n\n Ricapitolando, andremo ad utilizzare i seguenti servizi cloud:<\/p>\n\n\n\n Gli strumenti disponibili per la gestione di una infrastruttura di questo tipo sono numerosissimi: la CLI di AWS, la console o uno dei framework presenti oggi in rete come Troposphere, Terraform, o AWS SAM.<\/p>\n\n\n\n Come spiegato nell\u2019introduzione, il nostro obiettivo \u00e8 realizzare un\u2019applicazione backend velocemente, per questo motivo abbiamo scelto Serverless framework<\/strong>!<\/p>\n\n\n\n Serverless \u00e8 il framework scritto in Node.js che permette di gestire la lifecycle degli applicativi serverless. Ad oggi supporta numerosi Cloud Provider e funzionalit\u00e0.<\/p>\n\n\n\n Per quanto riguarda AWS, Serverless ci permetter\u00e0 di creare e gestire le risorse di cui abbiamo bisogno sul nostro account utilizzando uno stack di Cloudformation<\/strong>.<\/p>\n\n\n\n Prima di iniziare a lavorare sul progetto, \u00e8 necessario aver soddisfatto i seguenti requisiti:<\/p>\n\n\n\n Siccome andremo a scrivere un\u2019applicazione in TypeScript<\/strong>, Node.js sar\u00e0 il runtime environment su cui andremo ad eseguire il nostro codice compilato in JavaScript.<\/p>\n\n\n\n Per poter deployare lo stack di Cloudformation su AWS, \u00e8 necessario aver installato il Serverless framework sulla nostra macchina, tramite il comando npm install -g serverless, <\/em>e aver configurato correttamente le credenziali della AWS CLI.<\/p>\n\n\n\n Ora che abbiamo dato le opportune premesse ed elencato i requisiti, possiamo scaricare il progetto dal nostro repository GitHub<\/strong> e vedere come \u00e8 strutturato.<\/p>\n\n\n\n Iniziamo clonando il progetto:<\/p>\n\n\n\n git clone <\/em>https:\/\/github.com\/besharpsrl\/blog-serverless-backend-nodejs<\/em><\/a><\/p>\n\n\n\n Successivamente non ci rester\u00e0 che entrare nella cartella blog-serverless-backend-nodejs <\/em>e lanciare il comando npm install<\/em> per installare tutte le dipendenze necessarie. <\/p>\n\n\n\n Andiamo ad elencare quelle che meritano alcune precisazioni:<\/p>\n\n\n\n Nel file package.json <\/em>\u00e8 possibile trovare queste e altre dipendenze di utility, <\/em>oltre ai comandi che ci saranno utili per eseguire e testare le nostre API in locale e poi rilasciarle sull\u2019account AWS.<\/p>\n\n\n\n Aprendo il progetto sul vostro IDE noterete uno scaffolding di questo tipo:<\/p>\n\n\n Entriamo ora nel dettaglio delle logiche applicative.<\/p>\n\n\n\n Descriveremo un caso d\u2019uso molto semplice: delle semplici API REST che offrono delle CRUD functions per gestire dei libri di una biblioteca.<\/strong><\/p>\n\n\n\n La logica risiede sotto la directory book; <\/em>qui \u00e8 definito il Controller di tsoa<\/em>, il Service con la business logic e i modelli di sequelize.<\/em><\/p>\n\n\n\n Nel file app.ts <\/em>andremo invece a registrare le rotte Express, autogenerate da tsoa a partire dai Controller, per poi esportare un modulo che verr\u00e0 utilizzato come handler <\/em>per la nostra Lambda function.<\/p>\n\n\n\n Il tutto \u00e8 reso possibile dallo statement:<\/p>\n\n\n\n module.exports.handler = sls(app)<\/em><\/p>\n\n\n\n Con questo comando stiamo invocando una funzione del Serverless framework<\/strong> che andr\u00e0 a creare un wrapper attorno alle rotte Express. L\u2019handler della nostra AWS Lambda function sapr\u00e0 quindi a quale rotta Express dirottare le chiamate API REST. Proprio qui stiamo andando a definire le parti essenziali dell\u2019infrastruttura, come il Cloud Provider su cui andremo a deployare la nostra Lambda function, il suo handler (il modulo che abbiamo esportato nel file app.ts<\/em>) e i Lambda Layer da agganciare alla function.<\/p>\n\n\n\n Con questa configurazione, infatti, andremo a creare un Lambda Layer contenente tutti i node modules. Cos\u00ec facendo, alleggeriremo notevolmente le dimensioni della nostra funzione ottenendo vantaggi in performance<\/strong> durante la sua esecuzione.<\/p>\n\n\n\n Ma come verr\u00e0 invocata la Lambda function? Tramite gli events<\/em> di tipo http <\/em>definiti nel file di Serverless!<\/em> Questo permetter\u00e0 la creazione di un API Gateway con cui sar\u00e0 possibile richiamare la nostra funzione.<\/p>\n\n\n\n La risorsa col path \/api\/{proxy+}<\/em> sar\u00e0 quella che far\u00e0 da proxy alle rotte di backend. Abbiamo cos\u00ec creato un\u2019unica risorsa lato API Gateway che ci permetter\u00e0 di invocare tutte le REST API.<\/p>\n\n\n\n Vi piacerebbe poter testare le API senza doverle necessariamente rilasciare su AWS Lambda ad ogni modifica? Il pacchetto di Node.js serverless-offline <\/em><\/strong>\u00e8 ci\u00f2 che fa al caso nostro!<\/strong><\/p>\n\n\n\n Serverless-offline<\/em><\/strong> \u00e8 un plugin di Serverless che emula AWS Lambda e API Gateway sulla nostra macchina per velocizzare le attivit\u00e0 di sviluppo.<\/p>\n\n\n\n Prima di poterlo usare, per\u00f2, sar\u00e0 necessario creare un database Postgres<\/strong> in locale e lanciare le migrazioni di Sequelize:<\/p>\n\n\n\n Dalla root del progetto eseguiamo il comando<\/p>\n\n\n\n E poi:<\/p>\n\n\n\n Ora che abbiamo il database in locale, non ci resta che provare le API! Lanciamo il comando npm run start <\/em>per compilare il nostro codice TypeScript in JavaScript ed emulare la Lambda tramite il plugin serverless-offline<\/em>.<\/p>\n\n\n\n Non appena il comando avr\u00e0 completato la sua esecuzione, otterremo il seguente output sul terminale:<\/p>\n\n\n\n A questo punto siamo pronti per testare le API col nostro tool preferito (Postman, Curl). Utilizzando, ad esempio, il comando curl <\/em>possiamo eseguire da terminale il comando: <\/p>\n\n\n\n curl http:\/\/localhost:3000\/dev\/api\/book\/<\/em><\/p>\n\n\n\n Ci siamo: \u00e8 il momento di deployare l\u2019infrastruttura sul nostro account!<\/p>\n\n\n\n Lanciando il comando npm run deploy-dev<\/em> inizier\u00e0 il processo di rilascio<\/strong>. La prima volta apparir\u00e0 il seguente output:<\/p>\n\n\n\n Sull\u2019account AWS potremo vedere che \u00e8 stato generato uno stack Cloudformation contenente le risorse di cui abbiamo bisogno.<\/p>\n\n\n\n Accedendo alla console, tra la lista delle Lambda functions troveremo anche quella appena creata:<\/p>\n\n\n\n Prima di poter testare le API sar\u00e0 necessario lanciare le migrazioni di Sequelize<\/em> per creare le tabelle sul database Aurora Serverless. A questo punto possiamo lanciare la migrazione tramite il comando:<\/p>\n\n\n\n Una volta completate le migrazioni su Aurora Serverless, proviamo le API attraverso API Gateway.<\/p>\n\n\n\n Nell\u2019output del comando npm run deploy-dev <\/em>\u00e8 gia stato stampato l\u2019endpoint dell\u2019API Gateway, proviamolo subito:<\/p>\n\n\n\n curl https:\/\/sxfd74jes5.execute-api.eu-west-1.amazonaws.com\/dev\/api\/book\/<\/em><\/p>\n\n\n\n Il rilascio dell\u2019infrastruttura Serverless sull\u2019account AWS \u00e8 finito!<\/p>\n\n\n\n Per concludere, in questo articolo abbiamo visto come creare un\u2019applicazione Serverless su AWS Lambda utilizzando Node.js come runtime engine.<\/strong><\/p>\n\n\n\n Per scrivere il progetto abbiamo scelto TypeScript<\/strong> per avere il vantaggio di usare un linguaggio trascompilato, ampiamente supportato e conosciuto.<\/p>\n\n\n\n La scelta di Node.js come runtime engine ci ha permesso di avere un Cold Start time<\/strong><\/a> molto contenuto<\/strong> in quanto JavaScript viene direttamente interpretato dall\u2019engine. Inoltre, grazie alla configurazione utilizzata, il codice sorgente \u00e8 stato separato dalle dipendenze, salvate su Lambda Layer, abbattendo drasticamente le dimensioni del nostro sorgente e migliorando ulteriormente le performance di avvio.<\/p>\n\n\n\n Soddisfatti? \ud83d\ude42<\/p>\n\n\n\nServizi utilizzati<\/h2>\n\n\n\n
<\/figure><\/div>\n\n\n
\n
Gestione dell\u2019infrastruttura: il Serverless framework<\/h2>\n\n\n\n
Prerequisiti<\/h2>\n\n\n\n
\n
Hands on!<\/h2>\n\n\n\n
\n
<\/figure><\/div>\n\n\n
Infine, non ci resta che analizzare il file serverless.yml<\/em>:<\/p>\n\n\n\nservice: express-serverless\nprovider:\n name: aws\n runtime: nodejs12.x\n region: eu-west-1\n stage: ${env:NODE_ENV}\n environment:\n NODE_ENV: ${env:NODE_ENV}\n iamRoleStatements:\n - Effect: 'Allow'\n Action:\n - 'secretsmanager:GetSecretValue'\n Resource:\n - '*'\n\npackage:\n exclude:\n - node_modules\/**\n - defer\" defer\" defer\" defer\" defer\" defer\" defer\" defer\" defer\" defer\" defer\" defer\" defer\" defer\" defer\" src\/**\n\nlayers:\n nodeModules:\n path: layer\n compatibleRuntimes:\n - nodejs12.x\n\n\u2026..\n\nfunctions:\n app:\n handler: dist\/app.handler\n layers:\n - {Ref: NodeModulesLambdaLayer}\n events:\n - http:\n path: \/api\n method: ANY\n cors: true\n - http:\n path: \/api\/{proxy+}\n method: ANY\n cors: true\n vpc:\n securityGroupIds:\n - {Ref: LambdaSecurityGroup}\n subnetIds:\n - \"subnet-1\"\n - \"subnet-2\"\n - \"subnet-3\"\n\nplugins:\n - serverless-offline<\/pre>\n\n\n\n
Local testing<\/h2>\n\n\n\n
docker-compose up -d<\/pre>\n\n\n\n
npm run migrate-db-local<\/pre>\n\n\n\n
<\/figure>\n\n\n\n
Deploy <\/h2>\n\n\n\n
<\/figure>\n\n\n\n
<\/figure>\n\n\n\n
Dobbiamo quindi poterci connettere al database su AWS. Creiamo una macchina bastion sull\u2019account e dirottiamo il traffico dalla nostra macchina al bastion. Utilizziamo il comando sshuttle:<\/em><\/p>\n\n\n\nsshuttle --dns -r ubuntu@EC2_BASTION_IP YOUR_VPC_CIDR --ssh-cmd 'ssh -i YOUR_PEM_KEY'<\/pre>\n\n\n\n
npm run migrate-dev<\/pre>\n\n\n\n
Conclusioni<\/h2>\n\n\n\n