Da macro a micro…tutto!

I microservizi, in pratica

Davide D'Antonio
weBeetle
15 min readMay 26, 2020

--

In questa seconda parte dell’articolo vedremo come realizzare un’applicazione di ticketing molto semplice utilizzando l’approccio a microservizi, Node.js (utilizzeremo il framework fastify) e Docker. Nello specifico l’applicazione avrà i seguenti microservizi:

  • auth: microservizio che si occuperà della gestione dell’autenticazione e della registrazione degli utenti.
  • users: microservizio che si occuperà della gestione degli utenti da parte di un amministratore di sistema.
  • tickets: microservizio che si occuperà della gestione dei ticket.

L’accesso ai microservizi appena descritti avverrà attraverso un’API gateway. In pratica alla fine di questo articolo avremo descritto il seguente schema architetturale:

Prima di iniziare

Assicuratevi di avere tutto ciò che serve per lo sviluppo e cioè:

  • Node.js: troverete tutte le informazioni utili all’installazione ed alla configurazione per la vostra specifica piattaforma sul sito ufficiale https://nodejs.org/en.
  • Docker: anche in questo caso tutte le informazioni utili al download ed all’installazione le trovate sul sito ufficiale https://www.docker.com/get-started.
  • Il vostro editor javascript preferito 🤪.
  • Postman per testare le rotte ed i microservizi che creeremo.
  • Un terminale unix like.
  • Conoscenze del framework Fastify (https://fastify.io), e ovviamente di Node.js

Creiamo la struttura del progetto

Il primo passo è quello di installare create-fastify-app (per chi non conosce questo modulo npm consiglio di leggere quest’articolo). Si tratta di un’utility che ci aiuta ad effettuare lo scaffolding di applicazioni fastify. Per installarlo sul nostro sistema basta digitare quanto segue dalla riga di comando.

Ora che create-fastify-app è installata avrete a disposizione il comando fastify-app. Digitando:

Bene!

Ora creiamo una directory e la chiamiamo ticketing-system. Da linea di comando entriamo in questa directory e digitiamo il seguente comando

e rispondiamo alle domande che ci vengono poste:

Ci siete? Perfetto!

Ora ripetiamo questo comando anche per tickets e users. Una volta finito creiamo anche un file docker-compose.yml nella root del progetto. Alla fine la struttura delle directory somiglierà a quanto segue:

Per il momento andremo avanti creando un microservizio alla volta. Iniziamo da quello di autenticazione.

Autenticazione

Ora che lo scheletro del nostro progetto è completo, iniziamo a scrivere il codice del nostro microservizio. Prima di iniziare però dobbiamo installare i plugin fastify di cui abbiamo bisogno.

Dalla directory principale del nostro microservizo di autenticazione digitiamo quanto segue.

JWT

Una volta terminata l’installazione, andiamo nella directory plugins e creiamo il file jwt.js. Quando abbiamo terminato scriviamo il seguente codice all’interno del file:

Per queste linee di codice utilizziamo fastify-jwt che decora la nostra istanza fastify con i metodi standard di jsonwebtokens ossia decode, sign, e verify. Inoltre saranno disponibili anche i metodi request.jwtVerify e reply.jwtSign , e vedremo fra non molto come utilizzarli.

MongoDB

Ora che abbiamo configurato il plugin jwt facciamo lo stesso con quello MongoDB, quindi creiamo un file mongo.db.js all’interno della directory plugins e scriviamo il seguente codice al suo interno:

Anche in questo caso utilizziamo un plugin sviluppato dal core team di fastify, ossiafastify-mongodb , che decora il nostro microservizio aggiungendogli la possibilità di connettersi ad un istanza MongoDB. Un’ulteriore cosa che faremo ora è aggiornare il file docker-compose.yml nella directory principale del progetto:

Adesso dovrebbe essere anche più chiara la configurazione del plugin MongoDB fatta in precedenza, ossia la riga:

Per ora fermiamoci qui con il docker-compose.yml. Ci torneremo fra poco, quando dovremo configurare anche il microservizio auth.

Implementiamo il servizio

Ora che abbiamo tutti gli elementi di contorno configurati, implementiamo il cuore del nostro microservizio. Per farlo ci spostiamo nella directory services e creiamo il file auth.js e iniziamo a definire quanto segue:

In auth.js abbiamo definito due rotte:

  • POST /signup : che consente la registrazione di un utente
  • POST /signin : che consente, invece ad un utente di effettuare il login

Entrambe le rotte hanno uno schema ben definito. Questo perché Fastify utilizza un approccio basato su schema e, anche se non è obbligatorio, io consiglio vivamente di utilizzare JSON Schema per validare le nostre rotte e serializzare i risultati. Internamente, Fastify compila lo schema in una funzione altamente performante.

Sembra inutile commentare l’implementazione dei metodi. Anche perché se volessi farlo per tutti i microservizi mi dilungherei troppo e non è il caso 🤣. Dico solo che in entrambi i casi, nel momento in cui l’operazione di autenticazione o registrazione è andata a buon fine. Il microservizio restituisce un token che utilizzeremo per fare le invocazioni ad i prossimi servizi che creeremo.

Aggiorniamo il docker-compose

Ora che abbiamo definito la struttura - e lo schema - del nostro microservizio di autenticazione, aggiorniamo il docker-compose.ym. Prima però creiamo un Dockerfile all’interno della directory auth e copiamo quanto segue al suo interno:

Una volta che abbiamo finito aggiorniamo il nostro docker-compose.yml con le seguenti informazioni:

Una volta terminato dirigiamoci nella directory principale del progetto e digitiamo:

Se tutto è andato a buon fine, avrete a disposizione il servizio di autenticazione che abbiamo creato all’indirizzo http://localhost:3001 . Ora non vi resta di verificare con Postman che tutto funzioni.

La gestione dei Tickets

Ora che abbiamo terminato la nostra autenticazione passiamo all’implementazione del microservizio che si occuperà di gestire i ticket aperti da un utente.

Dalla directory principale del nostro microservizo di autenticazione digitiamo quanto segue.

JWT

L’implementazione è del tutto identica a quella utilizzata nell’esempio precedente. Andiamo nella directory plugins , creiamo il file mongo.db.js e copiamo quanto già fatto per il microservizio di autenticazione. L’unica cosa da fare è modificare il file hooks/preHandler.js in modo che abbia il seguente aspetto:

Questo pezzetto di codice farà in modo di controllare sostanzialmente le seguenti cose:

  1. Se la richiesta che stiamo effettuando al microservizio ha un token nell’header della richiesta.
  2. Se il token presente nell’header della richiesta è valido.

Nel caso in cui questi controlli vadano a buon fine. Nel vostro handler della richiesta, ovvero la funzione che gestisce effettivamente la richiesta, potrete accedere ai dati utente attraverso request.user . Siccome nella definizione della rotta abbiamo salvato nel nostro token di autenticazione le proprietà username e fullName , queste saranno accessibili ed eventualmente utilizzate dai vostri handler.

MongoDB

Anche in questo caso il codice è del tutto identico al microservizio di autenticazione.

Implementiamo il servizio

Ora che abbiamo installato e configurato in nostri plugins passiamo all’implementazione. I metodi che implementeremo in questa gestione ticket saranno i seguenti:

  • GET /api/ticket: ritornerà la lista di tutti i miei ticket
  • POST /api/ticket : si occuperà di ricevere i dati relativi al ticket e di salvarli nella collection tickets del nostro database.
  • DELETE /api/ticket/:id : anche se non si dovrebbe 😅, questo metodo si occuperà di eliminare il ticket con l’id ricevuto come parametro.
  • GET /api/ticket/:id : infine questo ticket si occuperà di ritornare i dati del ticket che ha l’id inviato come parametro nella richiesta.

Ora immaginando che un ticket abbia il seguente schema:

Scriviamo il codice del nostro microservizio nel file services/ticket.js:

Per una questione anche di leggibilità, noterete che in questo caso ho omesso gli schema per proteggere le nostre rotte. Troverete tutto il codice necessario al link del progetto che vi condivido alla fine dell’articolo.

Aggiorniamo il docker-compose

Ora, così come abbiamo fatto per il nostro microservizio di autenticazione, aggiorniamo il docker-compose.yml , prima di farlo però creiamo un Dockerfile all’interno della directory tickets e copiate quanto segue al suo interno:

Noterete che rispetto al microservizio precedente è stata cambiata solo la porta esposta. Una volta finito aggiorniamo il nostro docker-compose.yml con le seguenti informazioni:

Ora che abbiamo finito torniamo nella directory principale del progetto e digitiamo:

Se tutto è andato bene, avremo il microservizo di autenticazione che risponde all’indirizzo http://localhost:3001e quello della gestione tickets all’indirizzo http://localhost:3002/api/tickets . Ovviamente, quando effettuerete i test con Postman, assicuratevi di invocare il microservizio dei tickets aggiungendo nell’header delle richieste il token ricevuto dal microservizio di autenticazione.

La gestione utenti

La gestione utenti sarà del tutto simile a quella dei ticket. Quindi un semplice CRUD (Create, Read, Update, Delete) che gestisce le utenze. Non presenterò il codice sorgente del microservizio, perchè rischierei di essere solo ripetitivo e noioso. Quello che c’è da sapere è che alla fine dell’implementazione del microservizio avrete all’interno della directory users il seguente Dockerfile

e il docker-compose.yml avrà il seguente aspetto:

API Gateway

Ok, ora che abbiamo terminato l’implementazione dei nostri microservizi avremo tre container docker che espongono i loro servizi.

Sembra tutto ok! Perfetto abbiamo finito!

Nel caso in cui vogliamo creare un’applicazione che invoca i nostri microservizi dobbiamo solo ricordarci che:

  • http://localhost:3001 : a questo endpoint risponderà il microservizio di registrazione ed autenticazione.
  • http://localhost:3002/api/ticket : qui invece risponderà il microservizio che gestisce i tickets.
  • http://localhost:3003/api/user : qui risponderanno gli endpoints del microservizio di gestione utenti.

A tal proposito ripropongo la stessa GIF del precedente articolo:

Non sembra una buona soluzione vero? Dobbiamo rendere l’infrastruttura invisibile a chi la utilizza. Quindi quello che faremo ora è implementare un API Gateway utilizzando fastify-http-proxy . In questo modo l’utente si dovrà preoccupare di invocare solo un endpoint, in tutta l’applicazione. Il primo passo da compiere è quello di creare un nuovo progetto con CFA nella directory principale del progetto in modo che la struttura finale del progetto sia la seguente:

Una volta creato il progetto entrate nella directory gateway ed installate il seguente modulo:

Questo plugin inoltra tutte le richieste ricevute con un determinato prefisso (o nessuno) a un upstream. Tutti gli Hooks di fastify vengono applicati.

Una volta installato create tre file all’interno della directory service.

Il primo è auth.js:

Il file tickets.js

ed infine Il file users.js

Fatto ciò creiamo il Dockerfile per il nostro gateway:

e aggiorniamo il nostro docker-compose.yml nel seguente modo:

Ricordatevi di rimuovere la sezione expose a tutti gli altri microservizi. Questo è importante perché se in precedenza ogni microservizo esponeva una sua porta per consentirci di invocarli, ora tutto il traffico passerà dal servizio gateway, quindi l’unico microservizio che esporrà una porta sarà lui. Questo è anche il motivo per cui negli endpoint degli upstream non abbiamo utilizzato localhost ma il relativo nome del container utilizzati nel docker-compose.yml .

Ora, se tutto è andato come doveva andare, invocando il comando:

sulla porta 3000 risponderà il nostro gateway che inoltrerà tutte le richieste che effettuerete al microservizio di riferimento restituendovi le relative risposte.

Monitoring

Ora che abbiamo terminato l’implementazione dei nostri microservizi, come facciamo a monitorare eventuali errori? E se dovessimo individuare un eventuale collo di bottiglia?

Elastic APM

Elastic APM è un sistema di monitoraggio delle prestazioni dell’applicazione basato sullo stack elastic. Consente di monitorare i servizi software e le applicazioni in tempo reale, raccogliendo informazioni dettagliate sulle prestazioni sui tempi di risposta delle richieste in arrivo, query al database, chiamate alla cache, richieste HTTP esterne e molto altro. Ciò semplifica l’individuazione e la risoluzione rapida dei problemi di prestazioni.

Elastic APM raccoglie anche automaticamente errori ed eccezioni non gestite. Gli errori vengono raggruppati principalmente in base allo stacktrace, quindi è possibile identificare nuovi errori man mano che si verificano e tenere d’occhio per quante volte.

Le metriche sono un’altra importante fonte di informazioni durante il debug dei sistemi di produzione. Gli agenti elastic APM raccolgono automaticamente le metriche di base a livello di host e le metriche specifiche dell’agent, come le metriche di runtime di Node.js.

Installazione e configurazione

Ora che abbiamo identificato come monitorare i nostri microservizi dobbiamo effettuare ancora qualche modifica al nostro progetto.

La prima cosa da fare è quella di modificare il nostro file docker-compose.yml in modo che assuma il seguente aspetto:

Modifichiamo i microservizi

Una volta modificato il docker-compose.yml passiamo alla modifica dei singoli microservizi. La prima cosa da fare è installare una nuova dipendenza e cioè:

Fatto ciò creiamo un file apm.js nella directory base di ogni microservizio. Il suo contenuto è sempre lo stesso:

Adesso dobbiamo solo modificare il package.json :

Perfetto! Ora dobbiamo solo dirigerci nella directory base del progetto e digitare:

vedrete che verranno installate tutte le dipendenze che mancavano e che salirà l’intera infrastruttura che abbiamo creato.

Ora, se tutto è andato bene, date uno sguardo all’url http://localhost:5601/app/apm e vi ritroverete davanti qualcosa de tipo:

e se provate a curiosare all’interno di uno specifico microservizio, per esempio tickets, dopo aver utilizzato un po’ l’API:

--

--

Davide D'Antonio
weBeetle

👨‍🎓Degree in computer science 💑 Married with Milena 🤓 Huge Nerd! 💻 Code lover 👨🏻‍💻 Fullstack developer