Een reactieve web applicatie bouwen met Vert.x

Dit is het eerste artikel in een serie artikelen: Een reactieve web applicatie bouwen met Vert.x

Voorwoord

Vandaag gaan we beginnen aan een nieuwe applicatie om een interessante Java API genaamd “Vert.x” te demonstreren. Dit artikel is onderdeel van een serie van artikelen over deze API. In deze editie gaan we wat grondwerk verrichten en introduceren we Vert.x enigszins om te laten zien welke componenten we allemaal gaan gebruiken van deze API. Omdat de voorbeeld-applicatie geschreven wordt tijdens het schrijven van deze artikelen, kan het zijn dat we later meer of minder componenten gaan gebruiken of dat we later besluiten om dingen toe te voegen of weg te halen qua functionaliteit. In een aantal iteraties gaan we in elk geval toewerken naar een prototype van een applicatie met als doel Vert.x te ontdekken.

Omdat we het artikel praktisch willen maken gaan we niet te diep in op de theorie van Vert.x. Dit wordt uitgebreid beschreven in de documentatie: https://vertx.io/docs/. Toch gaan we wel een kleine introductie geven om wat context te schetsen.

Na deze introductie gaan we direct aan de slag met het bouwen van de applicatie. Gaandeweg raken we de dingen die belangrijk zijn om uit te leggen en gaan we eventueel dieper in op de materie. Bij de bouw van de applicatie gaan we ons ook niet focussen op bepaalde “best-practices” qua ontwerp. Hier en daar nemen we bepaalde shortcuts omdat het makkelijker is en omdat we ons met name richten op de API.

Van de lezer wordt verwacht dat hij of zij ervaring heeft met Java en Maven. Andere vaardigheden zijn niet per se vereist, al is het handig als je RxJava of een soortgelijke manier van programmeren kent.

Introductie

Vert.x is een API die ontwikkelaars in staat stelt event driven, non-blocking applicaties te ontwikkelen. De API is in de basis licht en modulair opgebouwd.

Vert.x bestaat uit een aantal modules waarbij alleen de zogenaamde “core” module verplicht is. Die is overigens al vrij uitgebreid. Je kunt er, bijvoorbeeld, al een web-server mee ontwikkelen. Als je meer functionaliteit nodig hebt, kun je eenvoudig modules toevoegen.

Twee van de belangrijkste concepten van Vert.x zijn de verticles en het feit dat verticles met elkaar communiceren via de event bus. Het is belangrijk je te realiseren dat verticles de onderdelen van de applicatie zijn die je deployt en dat deze in feite de micro-services definiëren van de applicatie.

Alles binnen Vert.x is in principe asynchroon. Uiteraard kunnen er ook blokkerende activiteiten worden afgehandeld, maar dit wordt dan expliciet gemaakt en het werk dat blokkerend is vindt plaats op een zogenaamde “worker thread”. Verticles handelen werk af dat beschikbaar wordt gesteld via een event loop. De event loop is een thread die oneindig doorloopt, enigszins te vergelijken met een oneindige while lus. Iedere instantie van een verticle is gekoppeld aan een event loop. Er zijn meerdere event loops per applicatie, standaard twee voor iedere CPU core.

De code die je schrijft in Vert.x heeft een callback achtige stijl. Je implementeert handlers die “iets doen” en als het stukje werk is afgerond, wordt een signaal afgegeven en eventueel aanvullende code uitgevoerd. Deze stijl van programmeren leidt er dan al gauw toe dat je geneste callback handlers krijgt wat de code onoverzichtelijk kan maken. Er is daarom ook een RxJava component die je in staat stelt de API in een vloeiende, “reactive”, manier te gebruiken.

Een meer uitgebreide beschrijving van Vert.x vind je, zoals al eerder gemeld, op de Vert.x site. Een heel duidelijk en toegankelijk artikel is hier te vinden:

https://vertx.io/docs/guide-for-java-devs/

De applicatie

Om Vert.x te demonstreren gaan we een webapplicatie bouwen die de gebruiker in staat stelt een CSV-bestand te importeren met webstatistieken. We gaan er vanuit dat deze statistieken op uurbasis zijn en dat ze de bezoekersaantallen in dat uur, de nieuwe bezoekersaantallen (gebruikers die voor het eerst de site bezoeken) en het aantal sessies bevat.

Deze data zal worden verrijkt met gegevens van het weer zodat kan worden bekeken wat de invloed is van het weer op de bezoekersaantallen van de website. De initiële versie van de applicatie zal deze logica nog niet bevatten maar uiteindelijk is het de bedoeling dat op basis van de weersvoorspellingen kan worden ingeschat wat de bezoekersaantallen zullen zijn in de toekomst.

Voor onze voorbeeld applicatie gaan we de volgende Vert.x componenten gebruiken:

  • Core
  • Web
  • RxJava2
  • Service Discovery
  • Config
  • Unit (unit test ondersteuning)

De artikelen in deze serie zullen de bovenstaande componenten verder in detail behandelen.

Niet genoemd in bovenstaande componenten is het “templating” component welke we gaan gebruiken in de front-end van de applicatie. Hoewel het wellicht een onderwerp van discussie kan zijn ben ik zelf niet zo’n voorstander van het gebruik van deze manier van templating omdat in dat geval het genereren van de HTML pagina’s over wordt gelaten aan de backend. Dit hoort naar mijn idee echter in frameworks die daar specifiek voor bedoeld zijn zoals Angular of Vue.js. Wel is het gebruik van de door Vert.x geboden templating component geschikt voor prototyping zodat we de functionaliteit kunnen demonsteren.

De voorbeeld applicatie wordt beschikbaar gesteld op mijn Git account. Deze aflevering is hier te vinden:

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

 

Laten we gaan beginnen met bouwen

Voor deze applicatie gaan we gebruik maken van Maven. Op de website van Vert.x kunnen we een Maven project genereren: https://start.vertx.io. Ik heb echter zelf altijd een vrij standaard manier van het beginnen van een nieuw project. Ik gebruik een “quickstart” Maven archetype en voeg dan zelf altijd een vrij standaard set van depencency’s toe die ik ga gebruiken voor de applicatie.

We gebruiken voor onze applicatie Vert.x versie 3.5.3. Voor de hele pom.xml verwijs ik graag door naar het GitHub project maar de dependency’s som ik hieronder wel even op.

De “core” component van Vert.x:

<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
</dependency>

 

Hoewel we met het “core” component al een webserver zouden kunnen bouwen, willen we in onze applicatie ook aanvullende functionaliteit gebruiken zoals routing. Hiervoor hebben we het vertx-web component nodig.:

<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
</dependency>

 

De vertx-service-proxy stelt ons in staat om proxy code te genereren van service implementaties. Op die manier kunnen we een referentie opnemen in bijvoorbeeld de Verticles en Vert.x zal dan zelf de communicatie naar de daadwerkelijke implementatie voor zijn rekening nemen via de event-bus. Het is wat lastig om dit kort en bondig uit te leggen dus later in de artikelen zal het waarschijnlijk wat duidelijker worden:

<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-service-proxy</artifactId>
</dependency>

 

Om onze code “reactive” te kunnen gebruiken, zullen we de vertx-rx-java2 dependency gebruiken:

<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-rx-java2</artifactId>
</dependency>

vertx-config biedt functionaliteit voor het configureren van de applicatie. De configuratie wordt dan geïnjecteerd in onze services:

<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-config</artifactId>
</dependency>

 

Omdat we een web site gaan aanroepen die ons de gegevens van het weer geeft, gaan we de vertx-web-client component gebruiken:

<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-client</artifactId>
</dependency>

 

Het testen van asynchrone services kan een behoorlijke uitdaging zijn. We gebruiken daarom de vertx-junit5 component die ons tools biedt om dit eenvoudiger te maken:

<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-junit5</artifactId>
<scope>test</scope>
</dependency>

 

Het initiële domein en de applicatie-onderdelen

In dit artikel zullen we ons focussen op het beginpunt van de applicatie met daarnaast een kale HttpServerVerticle. Tenslotte gaan we een eerste versie van het domein opmaken.

Om de applicatie te kunnen starten implementeren we een Verticle die de verschillende onderdelen van onze applicatie configureert en start. In de package com.ocs.analytics definiëren we een klasse genaamd AnalyticsApplication.

Omdat deze klasse een Verticle is, erft het van AbstractVerticle (een klasse uit de Vert.x API). We kunnen nu een paar methoden “overriden” zodat we het gedrag van deze Verticle kunnen implementeren.

In dit geval implementeren we enkel de start() methode. Hierin lezen we, met behulp van de klassen uit de vertx-config module, de configuratie uit zodat we deze later kunnen meegeven aan de andere onderdelen van de applicatie. De configuratie bestaat uit een eenvoudig JSON bestand (in de resources/config directory) met een aantal instellingen waar we later verder op ingaan.

Eerst declareren we een ConfigStoreOptions instantie voor onze app-conf.json bestand:

final ConfigStoreOptions mainConfigStore = new ConfigStoreOptions()
        .setType(“file”)
        .setConfig(new JsonObject() .put(“path”, “config/app-conf.json”));

 

We kunnen meerdere ConfigStoreOptions maken met bestanden, omgevingsvariabelen en andere configuratie items. Welke typen er allemaal worden ondersteunt staat uiteraard in de documentatie. Voor onze applicatie gaan we vandaag enkel een bestand gebruiken.

De configuratie wordt nu toegevoegd aan een ConfigRetrieverOptions en aan een ConfigRetriever meegegeven. We kunnen het nu gebruiken om de onderdelen van onze applicatie te voeden met deze configuratie (zie hieronder).

Het volgende belangrijke aspect van de implementatie van de start() methode is het starten van de verschillende onderdelen van de applicatie. Voor nu is dit enkel de HttpServerVerticle die we later gaan implementeren. Omdat we het RxJava component als dependency in Maven hebben opgenomen, kunnen we de Vert.x API “reactive” gebruiken:

final ConfigRetriever configRetriever = ConfigRetriever.create(this.vertx, options);
configRetriever
        .rxGetConfig()
        .flatMapCompletable(configuration ->

                Completable
                        .fromAction(() -> LOGGER.debug(“Deploying Analytics Application backend.”))
                        .andThen(this.vertx
                                .rxDeployVerticle(HttpServerVerticle.class.getName(), new DeploymentOptions().setConfig(configuration)))
.
toCompletable())

        .subscribe(() -> {
            LOGGER.debug(“Application deployed successfully.”);
            startFuture.complete();
        }, throwable -> {
            LOGGER.debug(“Application has not been deployed successfully due to:”, throwable);
            startFuture.fail(throwable);
        });

 

Mocht je nog nooit RxJava hebben gebruikt of met een andere reactive manier van programmeren hebben gewerkt dan kan het bovenstaande wat lastig te begrijpen zijn. Het belangrijkste is schuin gedrukt en bestaat uit het starten van de HttpServerVerticle waar we de configuratie (met de setConfig methode) aan meegeven. De HttpServerVerticle gaan we later implementeren.

Voor de volledige implementatie van bovenstaande methode kun je de code bekijken op het GitHub project.

In de volgende aflevering van deze serie gaan de HttpServerVerticle verder implementeren. Voor nu maken we de klasse aan en implementeren we een simpele start() methode die een regel logt en aangeeft dat de start is afgerond:

@Override
public void start(Future<Void> future) {
    LOGGER.debug(“Started HttpServerVerticle…”);
    future.complete();
}

Op deze manier kunnen we er in elk geval voor zorgen dat de applicatie opstart en het project te bouwen is met Maven. Het laatste aspect dat we gaan behandelen in dit artikel is het domein model.

Het belangrijkste doel van de applicatie is het importeren van informatie uit twee bronnen: een CSV bestand en een REST end-point. Voor deze applicatie gaan we het domein model afleiden van de informatie die we binnen krijgen, het model heeft verder naar verwachting niet veel logica. Het hele domein model kun je bekijken op GitHub.

We hadden al eerder benoemd dat componenten in een Vert.x applicatie met elkaar communiceren via de event-bus. Dit wordt over het algemeen gedaan middels JSON payloads en Vert.x biedt je functionaliteiten om het serializen en de-serializen makkelijk te maken.

Met de @DataObject annotatie geef je aan dat het om een klasse gaat die gebruikt wordt om te communiceren via de event-bus. Verder wordt er vereist dat je zelf een constructor implementeert die als parameter een instantie van JsonObject (ook een klasse binnen de Vert.x API) mee krijgt. Je moet vervolgens in deze constructor implementeren hoe dit JsonObject moet worden gemapt naar een instantie van zijn klasse. Hieronder als voorbeeld een van de domein objecten:

@DataObject
public class WeatherForecast {
    private final City city;
    private final List<WeatherItem> items;

    public WeatherForecast(City city, List<WeatherItem> items) {
        this.city = city;
        this.items = items;
    }
    public WeatherForecast(JsonObject jsonObject) {
        this.city = new City(jsonObject.getJsonObject(“city”));
        this.items = jsonObject
                .getJsonArray(“items”)
                .stream()
                .map(o -> new WeatherItem((JsonObject) o))
                .collect(Collectors.toList());
    }
(…rest of code…)
}

We zien hierboven twee constructors, eentje die we zelf binnen de applicatie zullen gebruiken om een instantie te declareren en eentje die door de API wordt gebruikt, wanneer een object vanuit de event bus moet worden gemapt naar een object.

Met het domein model en de onderdelen die we hebben geïmplementeerd, hebben we een minimale basis om verder te bouwen aan onze applicatie. Dit is een goed moment om de applicatie te bouwen met Maven en te starten.

We bouwen onze applicatie met het `mvn clean package` commando. In de volgende artikelen gaan we wat dieper in op hoe we applicaties, die ontwikkeld zijn met Vert.x, allemaal kunnen starten. Voor nu gaan we de IDE gebruiken om de applicatie te starten. Om dit te doen gebruiken we de volgende klasse als main class:

io.vertx.core.Launcher

De argumenten om deze klasse aan te roepen zijn “run”, gevolgd door het startpunt van onze applicatie:

run com.ocs.analytics.AnalyticsApplication

Weet, overigens, dat Verticles ook afzonderlijk kunnen worden gestart, dat je er meerdere instanties tegelijk van kunt starten en dat je er aanvullende configuratie (vanaf de command-line) aan kunt meegeven.

Nu we onze “run configuration” hebben gedefinieerd, kunnen we de applicatie starten. De laatste regels in de console zouden er als volgt uit moeten zien:

10:47:21.019 [vert.x-eventloop-thread-0] DEBUG com.ocs.analytics.AnalyticsApplication – Deploying Analytics Application backend.
10:47:21.020 [vert.x-eventloop-thread-1] DEBUG com.ocs.analytics.application.HttpServerVerticle – Started HttpServerVerticle…
10:47:21.020 [vert.x-eventloop-thread-0] DEBUG com.ocs.analytics.AnalyticsApplication – Application deployed successfully.
Oct 07, 2018 10:47:21 AM io.vertx.core.impl.launcher.commands.VertxIsolatedDeployer
INFO: Succeeded in deploying verticle

 

Recap

Met de bovenstaande succesvolle uitvoering van de applicatie zijn we aan het einde gekomen van deze eerste editie van de serie. We hebben in dit artikel een nieuw project gestart en hebben wat componenten gedefinieerd waar we later aan verder gaan werken. Ook hebben we een eerste versie van het domein opgesteld.

In de volgende aflevering gaan we verder werken aan de web server en gaan we een begin maken met het importeren van het CSV bestand.

Meer weten?

Ook een Vert.x applicatie bouwen of eens in gesprek gaan? Neem volkomen vrijblijvend contact met ons via +31 40 3041330 of info@opencirclesolutions.com. Je kunt ook het formulier onderaan de pagina gebruiken om je vraag te stellen. Wij nemen dan snel contact met je op.

Tutorials

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