Introduzione
Vert.x [1] è una piattaforma su cui è possibile rilasciare ed eseguire le proprie applicazioni. È simile, per certi aspetti, a Node.JS [2] ma con una grande differenza: gira in una JVM, è scalabile, concorrente, non bloccante, distribuito e poliglotta.
Sviluppato originariamente con il nome di Node.x da Tim Fox — uno sviluppatore di VMware — ha una storia relativamente breve: la prima versione è datata 2011. Per evitare problemi legali con Node.js, il nome è stato modificato nell’attuale Vert.x mantenendo lo stesso significato: non a caso “vertex” è sinonimo di “nodo” in matematica. Attualmente il progetto è mantenuto dalla Eclipse Foundation dopo essere fuoriuscito da WMware nell’agosto 2013.
Vert.x utilizza come strato di I/O la libreria Netty: essendo semplicemente un toolkit, si può decidere se utilizzarlo direttamente per lo sviluppo delle proprie applicazioni o integrarlo in applicazioni o framework esistenti.
Le funzionalità del core occupano poco spazio sia in termini di codice che di memoria.
Verticles
Un’applicazione Vert.x consiste di uno o più componenti chiamati Verticle. questi sono pezzi di codice che il motore di Vert.x esegue. Ogni verticle viene eseguito in maniera concorrente rispetto agli altri e non c’è uno stato che viene condiviso.
Tradotto in parole semplici, si riesce a creare applicazioni multi-threaded senza dover gestire problematiche di concorrenza come la sincronizzazione o i lock tra thread.
Tipologie di verticle
Leggendo la documentazione ufficiale possiamo trovare 3 tipi di Verticle:
- Standard verticles: i più semplici, vengono eseguiti usando il thread dell’event-loop.
- Worker verticles: vengono eseguiti in thread separati presi da un pool e ogni istanza non è mai eseguita in concorrenza con altri verticles.
- Multi-threaded worker verticles: sono anch’essi eseguiti su thread del pool ma vengono eseguiti in maniera concorrente con gli altri verticles.
Event Bus
L’Event Bus è il cuore di Vert.x. Tutte comunicazioni tra verticle avvengono tramite questo componente, semplicemente attraverso lo scambio di messaggi. I verticle possono mandare o ascoltare eventi attraverso il bus con le modalità punto–punto, pub–sub o request–replay.
Altra caratteristica importante è che l’event bus è distribuito di default; in pratica, affinché comunichino fra loro, non siamo obbligati a rilasciare i verticles sulla stessa JVM: basta creare un Event Handler e registrarsi su uno specifico indirizzo per utilizzare il bus di eventi.
La base dell’implementazione è il pattern Multi Reactor che prevede di creare event loop multipli. Il fatto che un verticle abbia un solo handler collegato a un indirizzo del bus fa sì che non possa essere eseguito in maniera concorrente rispetto agli altri event loop.
Inoltre il numero di event loop viene definito in base al numero di CPU a disposizione, permettendo di scalare molto facilmente in caso di necessità perché basta aumentare il numero di CPU a disposizione.
Blocco o non blocco
Se, ad esempio, un verticle deve eseguire molte operazioni di I/O per lungo tempo, la coda degli eventi rimane bloccata fintanto che le operazioni non sono finite. Si presenta pertanto il problema che nessun altro messaggio può essere instradato agli altri verticle.
Come abbiamo visto sopra, esistono differenti tipi di verticle: utilizzando un verticle di tipo worker, possiamo evitare il blocco dell’applicazione mentre attende che un verticle finisca il suo compito. C’è anche la possibilità di eseguire codice bloccante nell’Event Loop senza bloccare tutto.
Concorrenza e scalabilità (verticale)
Come detto precedentemente, un verticle gira in un suo thread e non espone direttamente il suo stato agli altri. Per riuscire a scalare esiste un flag di Vert.x che indica al sistema quante istanze di verticle instanziare all’avvio. Riassumendo, indica quanti thread concorrenti creare per ogni verticle di cui si effettua il deployment.
Vert.x si occuperà di utilizzare tutti i thread a disposizione in base al carico, permettendo di far fronte a carichi di richieste crescenti in base alle capacità della macchina su cui gira. Nel caso specifico di una risorsa di rete come potrebbe essere un web server, Vert.x crea un singolo server e usa un load balancer interno per suddividere le varie richieste.
Concorrenza e scalabilità (orizzontale)
Visto che l’Event Bus è distribuito, Vert.x offre la possibilità di distribuire i Verticles: tale possibilità è chiamata Clustering e risulta essere trasparente al codice sviluppato. L’implementazione base è fornita da hazelcast [3] ed è utilizzato per trovare i Verticles. Ovviamente essendo un semplice toolkit, nulla vieta di agganciare il proprio Cluster Manager.
Un vantaggio di questa soluzione è che — senza dover gestire nulla di complicato — nel caso di fallimento di un nodo, il nostro Verticle viene spostato su un altro nodo del cluster attivo, evitando così il fallimento dell’applicazione.
Poliglotta
Una peculiarità del toolkit è la possibilità di utilizzare linguaggi diversi per creare i singoli Verticle.
Attualmente il supporto riguarda i linguaggi:
- Java
- JavaScript
- Groovy
- Ruby
- Ceylon
- Scala
- Kotlin
Un esempio
L’implementazione, come vedremo, è identica per ogni linguaggio e anche le API sviluppate mantengono per tutti i linguaggi la stessa filosofia. Qui sotto potete vedere un’implementazione Java
import io.vertx.core.AbstractVerticle; public class Server extends AbstractVerticle { public void start() { vertx.createHttpServer().requestHandler(req -> { req.response() .putHeader(“content-type”, “text/plain”) .end(“Hello from Vert.x!”); }).listen(8080); } }
e un’implementazione JavaScript:
vertx.createHttpServer() .requestHandler(function (req) { req.response() .putHeader(“content-type”, “text/plain”) .end(“Hello from Vert.x!”); }).listen(8080);
Si tratta dello stesso server che stampa il classico messaggio di benvenuto. Come possiamo vedere la sintassi è la stessa e questo permette agli sviluppatori di concentrarsi sulle funzionalità e sulla qualità delle del codice, non dovendo imparare uno specifico linguaggio.
Altre componenti del core
Il toolkit è stato pensato per dare alcune facilitazioni ai programmatori. Vediamole di seguito.
JSON
Per quantro riguarda JSON, sono presenti due classi, JsonObject e JsonArray. La prima rappresenta un oggetto come una mappa con chiavi e valori e supporto dei valori nulli, mentre la seconda è una sequenza di valori eventualmente nulli.
Buffers
I buffers sono una sequenza di zero o più byte che possono essere letti o scritti e che si espandono man mano che si scrivono dati al suo interno.
TCP server e client
Vert.x permette di scrivere server o client TCP non bloccanti, ad esempio per implementare un server web o delle API REST, oppure per interrogare servizi esterni. Ovviamente c’è anche il supporto per SSL e HTTP/2.
Logging
C’è la possibilità di utilizzare il logger preferito: da Java Log a Log4J, a SLF4J a LogBack.
Host name resolution
Sono presenti delle classi che permettono di risolvere indirizzi IP non usando le funzionalità di Java e quindi rendendo il tutto non bloccante.
Estensioni aggiuntive
Oltre alla parte core, il toolkit ha una serie di funzionalità aggiuntive che servono a migliorare i comportamenti base o aggiungere funzionalità, rispettando sempre quella che è la filosofia alla base dello sviluppo di Vert.x.
Vert.x-Web
È stato sviluppato sulla parte core del toolkit prendendo i principi di Express da Node.js e di Sinatra da Ruby. Si possono sviluppare sia applicativi classici server-side, che RESTful, che applicazioni in real-time con tecnologia push. Alcune caratteristiche di Vert.x-Web sono:
- routing basato su path o metodi;
- path risolti con regular expression;
- estrazioni di parametri;
- content negotiation;
- gestione dei cookie;
- multipart form e file;
- sessioni;
- CORS;
- autenticazione (Basic, JWT);
- template;
Vert.x Web Client
Facilita la gestione di chiamate HTTP, con supporto di Json, Request, Response, e form. Integra la possibilità di gestione del polling e di HTTP/2
Vert.x Data Access
Si tratta di una serie di client creati per l’accesso ai vari DataStore, sempre in maniera asincrona:
- Mongo
- JDBC
- Redis
- SQL Common
- MySQL/PostgreSQL
Integration
C’è anche una serie di librerie aggiuntive per la gestione di SMTP, JCA Adapter, Rabbit MQ, Kafka, AMQP Bridge.
Rimandiamo in ogni caso al sito ufficiale [1] per l’elenco delle altre “estensioni” che coprono aspetti di DevOps, custering, sviluppo di microservizi con funzionalità di discovery e configurazione distribuita. E oltre a questi, ci sono “estensioni” che si occupano del testing dei componenti, di servizi come Proxy, SockJs, gRPC e del cloud con Openshift.
Conclusioni
In questa prima parte abbiamo solo fatto una rapida panoramica su Vert.x, con la speranza di aver incuriosito chi non lo conosceva.
Nei prossimi articoli della serie cercherò di mostrare almeno una parte della numerose funzioni che il toolkit mette a disposizione, creando una piccola applicazione. Vedremo come salvare dati su un DataStore, come utilizzare la componente web per generare contenuti statici e una piccola API Rest e infine, come utilizzare il sistema di code / messaggi.
Alla prossima puntata, con le basi del progetto e la definizione del nostro server web.