Introduzione
Dopo aver visto come testare la nostra applicazione utilizzando le funzionalità del toolkit, passiamo ora a mettere un po’ di codice sul fuoco, andando ad implementare la parte che si occupa dell’accesso ai dati, come provvede alla comunicazione tra differenti Verticle, tramite l’event bus. Questa è una caratteristica nativa di Vert.x [1] creato per la gestione delle comunicazione in maniera asincrona e non bloccante.
Vi ricordo che trovate tutto il codice sul mio account github [2]: viene diviso per branch e riportato sul master (usiamo git) una volta completata la stesura dell’articolo.
Il DataBase
Cominciamo come di consueto a creare il nostro branch di lavoro (data-store). Aggiungiamo una directory di nome conf sotto src/main dove andremo a creare il nostro file di configurazione.
Vert.x prevede come formato dei file di configurazione il formato json. Al suo interno definiamo le info per la configurazione del datasource, come nel codice riportato sotto.
Come potete vedere ho scelto il database H2, che tramite una semplice dipendenza di maven [3] ci permette di avere tutto embedded alla nostra applicazione.
Andiamo ora a modificare il nostro pom.xml per aggiungere le dipendenze necessarie:
- H2 db (comprende anche i driver di accesso necessari);
- libreria per la definizione del DataSource;
- librerie aggiuntive del toolkit di Vert.x per la gestione asincrona dell’accesso ai dati;
- tool per la creazione automatica del DB e per la gestione delle modifiche in maniera automatica.
Più che altro per non annoiarvi, vi rimando direttamente al codice per vedere quali librerie ho utilizzato,.
Refactoring
Il refactor sul codice parte dalla classe Todo.java: nel metodo start ho modificato la sequenza dei deploy dei nostri verticle inserendo la parte che si occupa della gestione dell’accesso ai dati (DataStoreVerticle.java) e la chiamata a un metodo che inizializza il DB utilizzando Flyway [4].
Il tutto viene gestito tramite una composizione di Future, andando a verificare che tutte e tre le operazioni siano eseguite correttamente e non ci siano errori, come nel codice riportato di seguito.
Il metodo all di CompositeFuture si occupa di fare la verifica che le tre operazioni che restituiscono una Future siano completate correttamente; in caso di errore, eventualmente il metodo all si occuperà di gestire la fine dell’applicazione.
Non mi dilungo troppo su Flyway che da solo meriterebbe due articoli… Dirò solamente che Flyway è un’ottima libreria di versioning dei DB: ci permette di creare degli script SQL che inizializzano il DB o modificano la situazione attuale in maniera automatica, tenendo traccia del numero di versione applicato. Vi rimando al sito [4] per ulteriori approfondimenti.
Lanciare l’applicazione
Lanciamo la nostra applicazione:
java -jar target\todolist-project-1.0-SNAPSHOT.jar -conf src\main\conf\app-conf.json
Scorrendo i log possiamo vedere Flyway che inizializza il nostro DB e i nostri verticles deployati. L’unica modifica è il metodo deploy che adesso, grazie all’oggetto DeploymentOptions, permette di passare la nostra configurazione esterna.
DataStoreVerticle
DataStoreVerticle è la classe che si occupa di eseguire le query SQL per la manipolazione delle nostre “cose da fare”. Essendo la classe implementata come Verticle, per poter comunicare con altri, deve utilizzare per forza un qualche tipo di comunicazione.
Per risolvere questo problema in maniera asincrona e non bloccante, la soluzione che il nostro toolkit ci offre è di utilizzare l’Event Bus.
Nel codice qui sopra, possiamo vedere lo scheletro della nostra classe con i metodi che si occupano di implementare il CRUD della nostre attività.
Nel metodo start() ci occupiamo di creare una connessione al DB e, una volta ottenutala, passiamo a creare gli handler necessari che sono in ascolto sul nostro bus di eventi. Tutto questo usando le Future [5] di Java.
Event Bus
Come più volte detto, Vert.x utilizza un Event Bus per comunicare con i vari verticles o, più in generale, con le varie istanze deployate.
La semplicità di utilizzo è imbarazzante: il toolkit non utilizza nessuno schema particolare per definire gli indirizzi, ma usa semplicemente delle stringhe per definire i canali. Se proprio volgiamo avere una definizione, allora possiamo utilizzare i punti (dot notation) per definire il namespace, per esempio: europe.news.feed
Messaggistica
Come paradigma per mandare i messaggi possiamo usare:
- Publish / Subscribe
- Point to Point
- Request – Response
In base alle nostre esigenze abbiamo quindi massima libertà di utilizzo. Per mantenere semplice l’utilizzo, Vert.x utilizza come metodo per l’invio dei messaggi il “Best Effort Delivery”: detto in parole semplici, non abbiamo la garanzia che il messaggio sia consegnato in caso di problemi che possono esserci sul bus.
Per il contenuto dei messaggi, possiamo utilizzare sia qualunque primitiva che il linguaggio ci mette a disposizione, sia i buffer tipici di Vert.x. La maniera più semplice risulta essere quella di mandare un Json dei nostri dati, ma nulla ci vieta di definire dei codec per implementare il nostro messaggio nella maniera a noi più congeniale.
Implementare il tutto si traduce più o meno con queste righe di codice:
final EventBus eventBus = getVertx().eventBus(); eventBus.consumer(“todo.find.all”, message -> findAll(message, conn.result()));
In soldoni, recuperiamo un’istanza del nostro EventBus e, con una semplice stringa, definiamo di voler “consumare” un canale specifico. Passiamo anche un handler che si occuperà di gestire il messaggio ed eseguire le operazioni necessarie. Il tutto con una logica di tipo Request – Response.
Implementazione
Sulla parte puramente di query al DB, non starò molto a dilungarmi: una volta creata la connessione, abbiamo alcuni metodi del Toolkit per eseguire letture, modifiche o l’inserimento di nuovi dati, il tutto con la possibilità di eseguire eventuali store procedure presenti, ma anche di effettuare sequenze di operazioni in batch mode, o di leggere grandi quantità di dati utilizzando gli stream.
Gestendo il tutto dal nostro verticle, abbiamo — automaticamente — la gestione delle connessioni al DB, soprattutto per quanto riguarda la chiusura delle risorse.
Qui sotto potete vedere il codice che gestisce la creazione di una nuova TODO e la lettura di una specifica.
Test: verifica del funzionamento
Ora che abbiamo messo insieme tutti i nostri handler per gestire la parte di lettura e modifica dei dati, verifichiamo che tutto funzioni.
Il problema è che non abbiamo ancora completato la nostra pagina web, almeno io… quindi l’unica soluzione che abbiamo è creare una classe di test e verificare che tutto funzioni.
La classe sarà simile a quella già creata per il test della nostra API Rest: avremo il nostro runner JUnit presente nel toolkit e tramite un oggetto DeploymentOptions, le properties che servono alla creazione del datasource.
Per il test non useremo un DB persistente su filesystem, ma utilizzeremo un DB temporaneo in memoria, in modo tale che ad ogni lancio del test venga ricreato il tutto e si possa avere sempre un ambiente pronto senza dati sporchi.
Per creare quanto appena detto, ci affideremo come visto precedentemente al nostro Flyway. Grazie a Vert.x, il codice effettivo dei test risulta essere semplice.
- recupero del riferimento all’EventBus;
- invio dei messaggi sui canali;
- verifica che l’operazione sia andata a buon fine, utilizzando le assert.
Qui sotto vi mostro due esempi di test, il primo per l’inserimento per una nuova todo e il secondo per la lettura tramite id.
Conclusioni
Siamo quasi arrivati alla fine di questa serie: nella prossima puntata limeremo le ultime cose che, con l’aggiunta del datastore e del codice implementato per far funzionare il nostro test, ha portato qualche problema al Verticle che gestisce la parte di API Rest. Lascio per ora ai lettori il compito di controllare cosa non va. Creeremo infine la nostra pagina web di gestione.
La mia idea è di fornire una carrellata sulle altre caratteristiche del toolkit, mettendo qualche snippet di codice relativo. Alla prossima.