Exemplo de integração Vertx e RxJava

Exemplo de integração Vertx e RxJava

1. Visão geral

RxJava é uma biblioteca popular para a criação de programas assíncronos e baseados em eventos, que se inspira nas principais ideias apresentadas pela iniciativaReactive Extensions.

Vert.x, um projeto sob o guarda-chuva deEclipse, oferece vários componentes projetados desde o início para alavancar totalmente o paradigma reativo.

Usados ​​juntos, eles podem provar ser uma base válida para qualquer programaJava que precise ser reativo.

Neste artigo, carregaremos um arquivo com uma lista de nomes de cidades e imprimiremos, para cada uma delas, a duração de um dia, do nascer ao pôr do sol.

Usaremos dados publicados do públicowww.metaweather.comREST API - para calcular a duração da luz do dia eRxJava comVert.x para fazer isso de forma puramente reativa.

2. Dependência do Maven

Vamos começar importandovertx-rx-java2:


    io.vertx
    vertx-rx-java2
    3.5.0-Beta1

No momento em que este artigo foi escrito, a integração entreVert.x e oRxJava 2 mais recente está disponível apenas como uma versão beta, que é, no entanto, estável o suficiente para o programa que estamos construindo.

Observe queio.vertx:vertx-rx-java2 depende deio.reactivex.rxjava2:rxjava, então não há necessidade de importar qualquer pacote relacionado aRxJava explicitamente.

A versão mais recente da integraçãoVert.x comRxJava pode ser encontrada emMaven Central.

3. Configuração

Como em todo aplicativo que usaVert.x,, começaremos a criar o objetovertx, o principal ponto de entrada para todos os recursosVert.x:

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

A bibliotecavertx-rx-java2 fornece duas classes:io.vertx.core.Vertxeio.vertx.reactivex.core.Vertx. Enquanto o primeiro é o ponto de entrada usual para aplicativos que são baseados exclusivamente emVert.x, o último é o que devemos usar para obter a integração comRxJava.

Continuaremos definindo objetos que usaremos mais tarde:

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

Vert.x'sFileSystem dá acesso ao sistema de arquivos de uma maneira reativa, enquantoVert.x'sHttpClient faz o mesmo paraHTTP.

4. Cadeia Reativa

É fácil em um contexto reativo concatenar vários operadores reativos mais simples para obter uma computação significativa.

Vamos fazer isso porour 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);

Vamos agora explorar como funciona cada pedaço lógico de código.

5. Nomes de cidades

A primeira etapa é ler um arquivo contendo uma lista de nomes de cidades, um nome por linha:

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

O métodorxReadFile() lê reativamente um arquivo e retorna umRxJava'sSingle<Buffer>. Então, obtivemos a integração que procuramos: a assincronicidade deVert.x em uma estrutura de dados deRxJava.

Há apenas um arquivo, então teremos uma única emissão deBuffer com todo o conteúdo do arquivo. Convertemos essa entrada emRxJava'sFlowablee mapeamos as linhas do arquivo para ter umFlowable que emite um evento para cada nome de cidade.

6. Descritor de cidade JSON

Tendo o nome da cidade, a próxima etapa é usarMetaweather REST API para obter o código identificador dessa cidade. Esse identificador será usado para obter os horários de nascer e pôr do sol da cidade. Vamos continuar a cadeia de invocações:

Vamos continuar a cadeia de invocações:

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

O métodosearchByCityName() usa oHttpClient que criamos na primeira etapa - para chamar o serviçoREST que fornece o identificador de uma cidade. Então, com o segundoflatMap(),, obtemos oBuffer contendo a resposta.

Vamos concluir esta etapa escrevendo o corpo desearchByCityName():

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.x'sHttpClient retorna umRxJava'sFlowable que emite a respostaHTTP reativa. Este, por sua vez, emite o corpo da resposta dividido emBuffers.

Criamos uma nova solicitação reativa para o URL adequado, mas observamos queVert.x requires the HttpClientRequest.end() method to be invoked para sinalizar que a solicitação pode ser enviada e também requer pelo menos uma assinatura antes queend() pudesse ser invocado com êxito.

Uma solução para conseguir isso é usarRxJava'sdoOnSubscribe() para invocarend() assim que um consumidor fizer a assinatura.

7. Identificadores de cidade

Agora só precisamos obter o valor da propriedadewoeid do objetoJSON retornado, que identifica exclusivamente a cidade por meio de um método personalizado:

.map(extractingWoeid())

O métodoextractingWoeid() retorna uma função que extrai o identificador da cidade deJSON contido na resposta de serviçoREST:

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

Observe que podemos usar os métodos úteistoJson…() fornecidos porBuffer para obter acesso rápido às propriedades de que precisamos.

8. Detalhes da cidade

Vamos continuar a cadeia reativa para recuperar os detalhes de que precisamos deREST API:

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

Vamos detalhar o métodogetDataByPlaceId():

static Flowable getDataByPlaceId(
  HttpClient httpClient, long placeId) {

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

Aqui, usamos a mesma abordagem que implementamos na etapa anterior. getDataByPlaceId() retorna umFlowable<HttpClientResponse>. OHttpClientResponse, por sua vez, emitirá a respostaAPI em blocos se for maior que alguns bytes.

Com o métodotoBufferFlowable(), reduzimos os blocos de resposta em um único para ter acesso ao objeto JSON completo:

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

9. Horas do pôr do sol e do nascer do sol

Vamos continuar adicionando à cadeia reativa, recuperando as informações em que estamos interessados ​​do objetoJSON:

.map(toCityAndDayLength())

Vamos escrever o métodotoCityAndDayLength():

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());
    };
}

Ele retorna uma função que mapeia as informações contidas emJSON para criar umPOJO que simplesmente calcula o tempo em horas entre o nascer e o pôr do sol.

10. Inscrição

A cadeia reativa está completa. Agora podemos assinar oFlowable resultante com um manipulador que imprime as instâncias emitidas deCityAndDayLength, ou o rastreamento de pilha em caso de erros:

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

Quando executamos o aplicativo, pudemos ver um resultado assim, dependendo da cidade contida na lista e da data em que o aplicativo é executado:

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.

As cidades podem aparecer em uma ordem diferente da especificada no arquivo porque todas as solicitações paraHTTP API são executadas de forma assíncrona.

11. Conclusão

Neste artigo, vimos como é fácil misturar módulos reativos deVert.x com os operadores e construções lógicas fornecidas porRxJava.

A cadeia reativa que construímos, embora longa, mostrou como torna bastante fácil escrever um cenário complexo.

Como sempre, o código-fonte completo está disponívelover on GitHub.