Exemple d’intégration de Vertx et RxJava

Exemple d'intégration de Vertx et RxJava

1. Vue d'ensemble

RxJava est une bibliothèque populaire pour créer des programmes asynchrones et basés sur des événements, elle s'inspire des principales idées avancées par l'initiativeReactive Extensions.

Vert.x, un projet sous l'égide deEclipse, propose plusieurs composants conçus à partir de zéro pour tirer pleinement parti du paradigme réactif.

Utilisés ensemble, ils pourraient s'avérer être une base valable pour tout programmeJava qui doit être réactif.

Dans cet article, nous allons charger un fichier avec une liste de noms de villes et nous imprimerons, pour chacune d'elles, la durée d'une journée, du lever au coucher du soleil.

Nous utiliserons les données publiées du publicwww.metaweather.comREST API - pour calculer la durée de la lumière du jour etRxJava avecVert.x pour le faire de manière purement réactive.

2. Dépendance Maven

Commençons par importervertx-rx-java2:


    io.vertx
    vertx-rx-java2
    3.5.0-Beta1

Au moment de la rédaction de cet article, l’intégration entreVert.x et le nouveauRxJava 2 n’est disponible qu’en version bêta, ce qui est cependant suffisamment stable pour le programme que nous construisons.

Notez queio.vertx:vertx-rx-java2 dépend deio.reactivex.rxjava2:rxjava, il n'est donc pas nécessaire d'importer explicitement un package associé àRxJava.

La dernière version de l'intégration deVert.x avecRxJava peut être trouvée surMaven Central.

3. Installer

Comme dans toute application utilisantVert.x,, nous allons commencer à créer l’objetvertx, le point d’entrée principal de toutes les fonctionnalités deVert.x:

Vertx vertx = io.vertx.reactivex.core.Vertx.vertx();

La bibliothèquevertx-rx-java2 fournit deux classes:io.vertx.core.Vertx etio.vertx.reactivex.core.Vertx. Alors que le premier est le point d’entrée habituel pour les applications basées uniquement surVert.x, ce dernier est celui que nous devons utiliser pour obtenir l’intégration avecRxJava.

Nous continuons à définir les objets que nous utiliserons plus tard:

FileSystem fileSystem = vertx.fileSystem();
HttpClient httpClient = vertx.createHttpClient();

Vert.x deFileSystem donne accès au système de fichiers de manière réactive, tandis queVert.x deHttpClient fait de même pourHTTP.

4. Chaîne réactive

Dans un contexte réactif, il est facile de concaténer plusieurs opérateurs réactifs plus simples pour obtenir un calcul significatif.

Faisons cela pourour example:

fileSystem
  .rxReadFile("cities.txt").toFlowable()
  .flatMap(buffer -> Flowable.fromArray(buffer.toString().split("\\r?\\n")))
  .flatMap(city -> searchByCityName(httpClient, city))
  .flatMap(HttpClientResponse::toFlowable)
  .map(extractingWoeid())
  .flatMap(cityId -> getDataByPlaceId(httpClient, cityId))
  .flatMap(toBufferFlowable())
  .map(Buffer::toJsonObject)
  .map(toCityAndDayLength())
  .subscribe(System.out::println, Throwable::printStackTrace);

Voyons maintenant comment fonctionne chaque bloc logique de code.

5. Noms de ville

La première étape consiste à lire un fichier contenant une liste de noms de villes, un nom par ligne:

fileSystem
 .rxReadFile("cities.txt").toFlowable()
 .flatMap(buffer -> Flowable.fromArray(buffer.toString().split("\\r?\\n")))

La méthoderxReadFile() lit de manière réactive un fichier et renvoie unRxJava deSingle<Buffer>. Nous avons donc l’intégration que nous recherchons: l’asynchronisme deVert.x dans une structure de données deRxJava.

Il n’y a qu’un seul fichier, nous obtiendrons donc une seule émission deBuffer avec le contenu complet du fichier. Nous convertissons cette entrée en unRxJava deFlowable et nous mappons à plat les lignes du fichier pour avoir unFlowable qui émet un événement pour chaque nom de ville à la place.

6. Descripteur de ville JSON

Ayant le nom de la ville, l'étape suivante consiste à utiliser lesMetaweather REST API pour obtenir le code d'identification de cette ville. Cet identifiant sera ensuite utilisé pour obtenir les heures de lever et de coucher du soleil pour la ville. Continuons la chaîne d’appels:

Continuons la chaîne d’appels:

.flatMap(city -> searchByCityName(httpClient, city))
.flatMap(HttpClientResponse::toFlowable)

La méthodesearchByCityName() utilise lesHttpClient que nous avons créés à la première étape - pour invoquer le serviceREST qui donne l'identifiant d'une ville. Ensuite, avec le deuxièmeflatMap(),, nous obtenons leBuffer contenant la réponse.

Terminons cette étape en écrivant le corps dusearchByCityName():

Flowable searchByCityName(HttpClient httpClient, String cityName) {
    HttpClientRequest req = httpClient.get(
        new RequestOptions()
          .setHost("www.metaweather.com")
          .setPort(443)
          .setSsl(true)
          .setURI(format("/api/location/search/?query=%s", cityName)));
    return req
      .toFlowable()
      .doOnSubscribe(subscription -> req.end());
}

Vert.xHttpClient renvoie unFlowable deRxJava qui émet la réponseHTTP réactive. C'est à son tour émet le corps de la réponse divisé enBuffers.

Nous avons créé une nouvelle requête réactive à l'URL appropriée, mais nous avons noté queVert.x requires the HttpClientRequest.end() method to be invoked signale que la requête peut être envoyée et qu'il nécessite également au moins un abonnement avant que lesend() puissent être appelés avec succès.

Une solution pour y parvenir consiste à utiliserRxJava´sdoOnSubscribe() pour appelerend() dès qu’un consommateur s’abonne.

7. Identifiants de la ville

Il ne nous reste plus qu'à obtenir la valeur de la propriétéwoeid de l'objetJSON retourné, qui identifie de manière unique la ville via une méthode personnalisée:

.map(extractingWoeid())

La méthodeextractingWoeid() renvoie une fonction qui extrait l'identifiant de la ville desJSON contenus dans la réponse du serviceREST:

private static Function extractingWoeid() {
    return cityBuffer -> cityBuffer
      .toJsonArray()
      .getJsonObject(0)
      .getLong("woeid");
}

Notez que nous pouvons utiliser les méthodes pratiquestoJson…() fournies parBuffer pour accéder rapidement aux propriétés dont nous avons besoin.

8. Détails de la ville

Continuons la chaîne réactive pour récupérer les détails dont nous avons besoin dans lesREST API:

.flatMap(cityId -> getDataByPlaceId(httpClient, cityId))
.flatMap(toBufferFlowable())

Détaillons la méthodegetDataByPlaceId():

static Flowable getDataByPlaceId(
  HttpClient httpClient, long placeId) {

    return autoPerformingReq(
      httpClient,
      format("/api/location/%s/", placeId));
}

Ici, nous avons utilisé la même approche que nous avons mise en place à l'étape précédente. getDataByPlaceId() renvoie unFlowable<HttpClientResponse>. LeHttpClientResponse, à son tour, émettra la réponse deAPI en morceaux si elle est plus longue que quelques octets.

Avec la méthodetoBufferFlowable(), nous réduisons les segments de réponse en un seul pour avoir accès à l'objet JSON complet:

static Function>
  toBufferFlowable() {
    return response -> response
      .toObservable()
      .reduce(
        Buffer.buffer(),
        Buffer::appendBuffer).toFlowable();
}

9. Heures de coucher et de lever du soleil

Continuons à ajouter à la chaîne réactive, en récupérant les informations qui nous intéressent à partir de l'objetJSON:

.map(toCityAndDayLength())

Écrivons la méthodetoCityAndDayLength():

static Function toCityAndDayLength() {
    return json -> {
        ZonedDateTime sunRise = ZonedDateTime.parse(json.getString("sun_rise"));
        ZonedDateTime sunSet = ZonedDateTime.parse(json.getString("sun_set"));
        String cityName = json.getString("title");
        return new CityAndDayLength(
          cityName, sunSet.toEpochSecond() - sunRise.toEpochSecond());
    };
}

Il renvoie une fonction qui mappe les informations contenues dans unJSON pour créer unPOJO qui calcule simplement le temps en heures entre le lever et le coucher du soleil.

10. Abonnement

La chaîne réactive est terminée. Nous pouvons maintenant nous abonner auxFlowable résultants avec un gestionnaire qui imprime les instances émises deCityAndDayLength, ou la trace de pile en cas d'erreurs:

.subscribe(
  System.out::println,
  Throwable::printStackTrace)

Lorsque nous exécutons l'application, nous pouvons voir un résultat comme celui-ci, en fonction de la ville figurant dans la liste et de la date à laquelle l'application est exécutée:

In Chicago there are 13.3 hours of light.
In Milan there are 13.5 hours of light.
In Cairo there are 12.9 hours of light.
In Moscow there are 14.1 hours of light.
In Santiago there are 11.3 hours of light.
In Auckland there are 11.2 hours of light.

Les villes peuvent apparaître dans un ordre différent de celui spécifié dans le fichier car toutes les requêtes adressées auxHTTP API sont exécutées de manière asynchrone.

11. Conclusion

Dans cet article, nous avons vu à quel point il est facile de mélanger les modules réactifsVert.x avec les opérateurs et les constructions logiques fournis parRxJava.

La chaîne réactive que nous avons construite, bien que longue, a montré comment il est relativement facile d'écrire un scénario complexe.

Comme toujours, le code source complet est disponibleover on GitHub.