1. Overview
RxJava is a popular library for creating asynchronous and event-based programs, it takes inspiration from the main ideas brought forward by the Reactive Extensions initiative.
Vert.x, a project under Eclipse‘s umbrella, offers several components designed from the ground up to fully leverage the reactive paradigm.
Used together, they could prove to be a valid foundation for any Java program that needs to be reactive.
In this article, we’ll load a file with a list of city names and we’ll prints out, for each of them, how long is a day, from sunrise to sunset.
We’ll use data published from the public www.metaweather.com REST API – to calculate the length of the daylight and RxJava with Vert.x to do it in a purely reactive way.
2. Maven Dependency
Let’s start by importing vertx-rx-java2:
<dependency> <groupId>io.vertx</groupId> <artifactId>vertx-rx-java2</artifactId> <version>3.5.0-Beta1</version> </dependency>
At the time of writing, the integration between Vert.x and the newer RxJava 2 is available only as a beta release, that is, however, stable enough for the program we’re building.
Note that io.vertx:vertx-rx-java2 depends on io.reactivex.rxjava2:rxjava so there’s no need to import any RxJava related package explicitly.
The latest version of the Vert.x integration with RxJava can be found on Maven Central.
3. Set Up
As in every application that uses Vert.x, we’ll start creating the vertx object, the main entry point to all Vert.x features:
Vertx vertx = io.vertx.reactivex.core.Vertx.vertx();
The vertx-rx-java2 library provides two classes: io.vertx.core.Vertx and io.vertx.reactivex.core.Vertx. While the first is the usual entry point for applications that are uniquely based on Vert.x, the latter is the one we’ve to use to get the integration with RxJava.
We go on defining objects we’ll use later:
FileSystem fileSystem = vertx.fileSystem(); HttpClient httpClient = vertx.createHttpClient();
Vert.x‘s FileSystem gives access to the file system in a reactive way, while Vert.x‘s HttpClient does the same for HTTP.
4. Reactive Chain
It’s easy in a reactive context to concatenate several simpler reactive operators to obtain a meaningful computation.
Let’s do that for our 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);
Let’s now explore how each logical chunk of code works.
5. City Names
The first step is to read a file containing a list of city names, one name per line:
fileSystem .rxReadFile("cities.txt").toFlowable() .flatMap(buffer -> Flowable.fromArray(buffer.toString().split("\\r?\\n")))
The method rxReadFile() reactively reads a file and returns a RxJava‘s Single<Buffer>. So we got the integration we’re looking for: the asynchronicity of Vert.x in a data structure from RxJava.
There’s only one file, so we’ll get a single emission of a Buffer with the full content of the file. We convert that input into a RxJava‘s Flowable and we flat-map the lines of the file to have a Flowable that emits an event for each city name instead.
6. JSON City Descriptor
Having the city name, the next step is to use the Metaweather REST API to get the identifier code for that city. This identifier will then be used to get the sunrise and sunset times for the city. Let’s continue the chain of invocations:
Let’s continue the chain of invocations:
.flatMap(city -> searchByCityName(httpClient, city)) .flatMap(HttpClientResponse::toFlowable)
The searchByCityName() method uses the HttpClient we created in the first step – to invoke the REST service that gives the identifier of a city. Then with the second flatMap() we get the Buffer containing the response.
Let’s complete this step writing the searchByCityName()‘s body:
Flowable<HttpClientResponse> 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.x‘s HttpClient returns a RxJava‘s Flowable that emits the reactive HTTP response. This is turn emits the body of the response split in Buffers.
We created a new reactive request to the proper URL but we noted that Vert.x requires the HttpClientRequest.end() method to be invoked to signaling that the request can be sent and it also requires at least one subscription before end() could be successfully invoked.
A solution to accomplish that is to use RxJava‘s doOnSubscribe() to invoke end() as soon as a consumer subscribes.
7. City Identifiers
We now just need to get the value of the woeid property of the returned JSON object, which uniquely identifies the city through a custom method:
.map(extractingWoeid())
The extractingWoeid() method returns a function that extracts the city identifier from the JSON contained in the REST service response:
private static Function<Buffer, Long> extractingWoeid() { return cityBuffer -> cityBuffer .toJsonArray() .getJsonObject(0) .getLong("woeid"); }
Note that we can use the handy toJson…() methods provided by Buffer to quickly get access to the properties we need.
8. City Details
Let’s continue the reactive chain to retrieve the details we need from the REST API:
.flatMap(cityId -> getDataByPlaceId(httpClient, cityId)) .flatMap(toBufferFlowable())
Let’s detail the getDataByPlaceId() method:
static Flowable<HttpClientResponse> getDataByPlaceId( HttpClient httpClient, long placeId) { return autoPerformingReq( httpClient, format("/api/location/%s/", placeId)); }
Here, we used the same approach we’ve put in place in the previous step. getDataByPlaceId() returns a Flowable<HttpClientResponse>. The HttpClientResponse, in turn, will emit the API response in chunks if it is longer that few bytes.
With the toBufferFlowable() method we reduce the response chunks into a single one to have access to the full JSON object:
static Function<HttpClientResponse, Publisher<? extends Buffer>> toBufferFlowable() { return response -> response .toObservable() .reduce( Buffer.buffer(), Buffer::appendBuffer).toFlowable(); }
9. Sunset and Sunrise Times
Let’s keep on adding to the reactive chain, retrieving the information we’re interested in from the JSON object:
.map(toCityAndDayLength())
Let’s write the toCityAndDayLength() method:
static Function<JsonObject, CityAndDayLength> 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()); }; }
It returns a function that maps the information contained in a JSON to create a POJO that simply calculates the time in hours between sunrise and sunset.
10. Subscription
The reactive chain is completed. We can now subscribe to the resulting Flowable with a handler that prints out the emitted instances of CityAndDayLength, or the stack trace in case of errors:
.subscribe( System.out::println, Throwable::printStackTrace)
When we run the application we could see a result like that, depending on the city contained in the list and the date in which the application runs:
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.
The cities could appear in a different order than those specified in the file because all the requests to the HTTP API are executed asynchronously.
11. Conclusion
In this article, we saw how easy it is to mix Vert.x reactive modules with the operators and logical constructs provided by RxJava.
The reactive chain we built, albeit long, showed how it makes a complex scenario fairly easy to write.
As always, the complete source code is available over on GitHub.