Een reactieve web applicatie bouwen met Vert.x – deel 3

Voorwoord

In deze laatste aflevering gaan we de front-end en een aantal onderdelen van de back-end verder implementeren. De backend stelt ons inmiddels in staat om site statistieken te importeren en te verrijken met weerdata uit een weerstation ergens in Nederland (De Bilt).

Nu gaan we deze data opslaan in een gebruikerssessie en voeden aan onze webpagina zodat we grafieken van de data kunnen maken. Ook gaan we de data pagineren zodat we in de webpagina heen en weer kunnen “scrollen” tussen de pagina’s.

De code voor deze aflevering is te vinden in:

https://github.com/Piepers/site-analytics/tree/episode-3

Voor deze aflevering is weer behoorlijk wat code geproduceerd dus, net zoals in de vorige aflevering, nodig ik je uit om de code uit te checken en zelf te bestuderen, aan de hand van dit artikel.

De backend voorbereiden

Omdat we de pagina gaan uitbreiden met functionaliteit voor het tonen van een grafiek en het pagineren van data voor deze grafiek, gaan we een aantal dingen voorbereiden in de backend om dit mogelijk te maken.

Sessies

In de HttpServerVerticle slaan we sessies op met de verrijkte statistieken die de gebruiker heeft geladen. We hebben hiervoor een instantie van de klasse LocalSessionStore nodig uit de Vert.x web module. Deze declareren we in de HttpServerVerticle.

Op regel 40 zie je:

private LocalSessionStore sessionStore;

En vervolgens in de init() method van HttpServerVerticle:

sessionStore = LocalSessionStore.create(rxVertx);

Om het gebruik van sessies verder te activeren hebben we een CookieHandler en een SessionHandler nodig. In de start() methode van HttpServerVerticle maken we deze aan en koppelen we ze aan de router:

SessionHandler sessionHandler = SessionHandler.create(sessionStore);
router.route().handler(CookieHandler.create());
router.route().handler(sessionHandler::handle);

De routering van deze handlers moet aan het begin staan van de configuratie van de router.

Het valt je mogelijk op dat we naast de sessionStore ook nog een variabele localStatisticsStore hebben gedeclareerd in de HttpServerVerticle. In deze Map houden we de sessie-ids bij met de bijbehorende representatie van verrijkte statistieken zodat we kunnen pagineren. Hierover later meer.

Omdat de SessionStore wordt bijgehouden door Vert.x moeten we de localStatisticsStore wel periodiek opruimen. We gebruiken hiervoor een timer functionaliteit die standaard bij Vert.x zit:

rxVertx
.setPeriodic(THREE_MINUTES, handler -> {
(implementatie)
}

Mocht je interesse hebben in de details van de implementatie van deze timer: je vindt deze in regel 119 en verder in de HttpServerVerticle.

Routering

Vanaf regel 90 in de HttpServerVerticle hebben we een aantal end-points toegevoegd voor het pagineren van de statistieken. Momenteel hebben we de service zodanig geïmplementeerd dat we maximaal een jaar aan statistieken kunnen inladen en daarbij zoeken we de bijbehorende weerdata. Een jaar is natuurlijk veel te veel om in één keer in een grafiek te tonen, dus we bieden de mogelijkheid om te pagineren. Je ziet vanaf regel 90:

subRouter.get("/statistics/current").handler(this::getLatestSiteStatistics);        
subRouter.get("/statistics/first").handler(this::first);       
subRouter.get("/statistics/next").handler(this::next);       
subRouter.get("/statistics/previous").handler(this::previous);       
subRouter.get("/statistics/last").handler(this::last);

De implementatie van de end-points zorgt voor de paginering van de gegevens. Die implementatie gaan we kort bekijken in één van de volgende paragrafen.

Uitbreiding op de import

De grafiek zal de data tonen per uur. De x-as bevat de uren waarbij we per middernacht een tekst willen die een nieuwe dag aanduidt en vervolgens per uur een waarde op de y-as voor de temperatuur en het aantal bezoekers. We krijgen dus twee lijnen (en kunnen dit later eenvoudig uitbreiden met de aanwezige data) in de grafiek zoals we later in het artikel zullen zien.

Om de grafiek te voorzien van data gaan we hetgeen we hebben geïmporteerd en verrijkt met weerdata omzetten naar een pagineerbare representatie waarbij we het voor de front-end ook makkelijk maken om de data in de grafiek te plaatsen. Verder bieden we de mogelijkheid om te pagineren.

De logica voor dit alles zit in de SiteStatisticsDto klasse. Nadat we de gegevens hebben opgehaald en verrijkt maken we een instantie aan van de SiteStatisticsDto klasse met de verkregen SiteStatistics. We hebben hiertoe de importHandler() methode uitgebreid. Deze methode hebben we in de vorige afleveringen ook al gezien. Als we inzoomen op de implementatie waar het omzetten van de gegevens naar de SiteStatisticsDto gebeurt dan zien we vanaf regel 247 het volgende (dikgedrukt de meest relevante code):

Dit is nogal een verschil met wat we in aflevering 2 zagen. Toen stond er namelijk nog:

vertx
       .eventBus()
       .<JsonObject>rxSend("file-upload", jsonObject)
       .subscribe(message -> LOGGER.debug("Imported response: {}",
                 message.body()
                        .encode()),
                 throwable -> LOGGER.error("Something went wrong while importing the file.", throwable));

Destijds zonden we een bericht naar het event-bus adres: “file-upload”. De ImportProcessVerticle verwerkte het bestand en hetgeen we terugkregen toonden we ter voorbeeld in het log bestand.

 Wat gebeurt er allemaal in de nieuwe versie?

  1. Eerst halen we de SiteStatistics op uit het antwoord van de ImportProcessVerticle (just(new SiteStatistics(message.body))))
  2. De SiteStatisticsDto heeft een factory method ‘fromOrderedStatistics’ waarmee het de statistieken omzet naar een pagineerbare representatie.
  3. We roepen een methode aan (.first()) die naar de eerste pagina gaat.
  4. De dto voegen we toe aan de eerdergenoemde Map zodat we bij een latere request de session id kunnen correleren aan de dto die bij die sessie hoort.
  5. De eerste pagina (verkregen uit de aanroep naar first()) wordt naar de front-end verzonden.

Paginering

De logica voor het pagineren van de statistieken heeft niets met Vert.x te maken aangezien het slechts logica bevat om op basis van de gegevens per dag te pagineren. We gaan op de logica zelf dan ook niet heel diep in.

Wat wel interessant is om te melden is dat het object uiteindelijk JSON-informatie teruggeeft. Hiertoe hebben we de OneDayStatisticsDto klasse geannoteerd met @DataObject zodat we het object makkelijk kunnen transformeren naar een json repsentatie. De SiteStatisticsDto klasse bevat een getPageAsJson() methode om de Json representatie van een pagina aan te vullen met wat handige velden (deze dag-representatie zit dus in de OneDatStatisticsDto):

JsonArray result = new JsonArray(this.currentPage);
            return new JsonObject()
                .put("count", statistics.size())
                .put("startKey", keyFormatter.format(this.startKey))
                .put("endKey", keyFormatter.format(this.endKey))
                .put("sop", keyFormatter.format(this.sop))
                .put("eop", keyFormatter.format(this.eop))
                .put("page", result);

Voor de rest van de logica zou je in de code kunnen kijken.

Het verdiend niet de schoonheidsprijs en er zijn genoeg verbeteringen te bedenken maar de front-end kan met deze klasse dus door de data heen pagineren waarbij we ook nog een “first()” en “last()” methode hebben om naar het begin en het einde te gaan.

chart.js

Ook de implementatie van de grafiek is niet Vert.x specifiek maar omdat het leuk is om te zien hoe we de JSON-data eenvoudig kunnen verwerken in de grafiek, lichten we het toch even toe.

Om de gegevens te tonen op de webpagina werken we met een library genaamd chart.js. Dit is een open-source project die eenvoudig is toe te passen in onze pagina.

In de templates in de directory: /src/main/resources/templates zie je in header.ftl de import staan naar versie 2.8.0 van de library:

<script src=”https://cdn.jsdelivr.net/npm/chart.js@2.8.0″></script>

In het index.ftl bestand initialiseren we de “chart” wanneer de pagina geladen is (in de init()) methode, en vullen we de gegevens van de grafiek in de processData() methode.

Vanaf regel 77 zien we:

let chartData = {
...
}

Daar zetten we de gegevens in die we terugkrijgen van de server. De niet al te efficiënte code daarvoor is ervoor om de arrays te vullen van de grafieken:

let pages = statisticsData.page;
        let labelData = [];
        let tempData = [];
        let vititorData = [];
        pages.forEach(page => {
            let labels = page.labels;
            labels.forEach(lbl => labelData.push(lbl))
            let temps = page.tempData;
            temps.forEach(tmp => tempData.push(tmp));
            let users = page.usersData;
            users.forEach(usrs => vititorData.push(usrs));
       });

Nu kunnen we de geïmporteerde data per dag tonen en heen en weer pagineren:

En verder?

We hebben in de afgelopen drie afleveringen een Vert.x applicatie ontwikkeld die site statistieken in CSV formaat kan ontvangen, verwerken en verrijken met weer-data van een weerstation in Nederland. De applicatie zoals die nu is, is slechts een ruw prototype en er zijn vele mogelijkheden om de applicatie uit te breiden. Doel van deze afleveringen was om te laten zien hoe je met Vert.x een applicatie ontwikkelt en hoe je het zou kunnen inzetten als web applicatie. De functionaliteit wordt waardevoller als je de data van meerdere weerstations kunt kiezen, weerdata uit het buitenland zou kunnen verzamelen en meer uitgebreide data kunt tonen in de front-end. Verder zou de weerdata beschikbaar moeten zijn op storage waarbij je niet steeds naar een web-site van het KNMI zou moeten gaan om de data op te halen.

Vert.x kun je voor veel doeleinden gebruiken. De kracht zit ‘m in de asynchrone verwerking, het mult-reactor pattern, en de manier waarop dit gebeurt staat je toe om snel zeer goed presterende applicaties te bouwen.

Een aantal zaken heb ik ervaren als relatief lastig. Alle objecten die via de event-bus naar de diverse componenten van de applicatie worden verstuurd moet je mappen naar een Json object. En hoewel het niet moetilijk is om deze mapping te schrijven en de API je veel tools biedt om dit makkelijk te maken is het wel zo dat je redelijk veel “boiler-plate” code moet schrijven om deze mapping te bewerkstelligen.

Als je de RxJava componenten gebruikt is de leercurve tenslotte naar mijn idee vrij hoog. Het geheel vereist gewoon een heel andere denkwijze. Ook is het eenvoudig om buiten ontwerp-principes om te ontwikkelen omdat RxJava heel flexibel is en veel mogelijkheden biedt. Prettis is echter dat deze manier van ontwikkelen goed aansluit bij JavaScript frameworks en je op die manier meer richting full-stack development kunt groeien.

Omdat Vert.x polyglot is past het goed in een omgeving waar applicaties in verschillende talen worden geschreven. Een lichtgewicht component bouwen die met deze applicaties communiceert is relatief eenvoudig.

Ik heb Vert.x inmiddels voor twee projecten mogen gebruiken. De één is een web applicatie waar gebruikersinteractie centraal staat maar waarbij ook veel taken automatisch worden uitgevoerd. Hierbij wordt de gebruiker met feedback op de hoogte gehouden van de status. We gebruiken hiervoor het Stomp protocol.

De andere applicatie is meer een “collector” die grote hoeveelheden data verwerkt uit tekstbestanden en deze op gestructureerde wijze opslaat. De Verticles die verantwoordelijk zijn voor de verwerking van deze data zijn zogenaamde “worker verticles” (zie: https://vertx.io/docs/vertx-core/groovy/#worker_verticles) die doormiddel van timers bestanden ophalen en verwerken.

Deze afleveringen zijn geschreven door Bas Piepers. Bas is Java ontwikkelaar bij Open Circle Solutions.

Nieuwsbrief

Meld je nu aan voor Open Circle Stories en krijg een verzameling artikelen, tips, nieuws en verdiepingen in je mailbox.

Pin It on Pinterest

Share This