Пример интеграции с Vertx и RxJava

Пример интеграции Vertx и RxJava

1. обзор

RxJava - популярная библиотека для создания асинхронных программ и программ, основанных на событиях, она черпает вдохновение из основных идей, выдвинутых инициативойReactive Extensions.

Vert.x, проект под зонтикомEclipse, предлагает несколько компонентов, разработанных с нуля для полного использования реактивной парадигмы.

При совместном использовании они могут стать надежной основой для любой программыJava, требующей реагирования.

В этой статье мы загрузим файл со списком названий городов и распечатаем для каждого из них продолжительность дня, от восхода до заката.

Мы будем использовать данные, опубликованные из общедоступныхwww.metaweather.comREST API - для расчета продолжительности светового дня иRxJava сVert.x, чтобы сделать это чисто реактивным способом.

2. Maven Dependency

Начнем с импортаvertx-rx-java2:


    io.vertx
    vertx-rx-java2
    3.5.0-Beta1

На момент написания интеграция междуVert.x и новой версиейRxJava 2 доступна только в виде бета-версии, которая, тем не менее, достаточно стабильна для программы, которую мы создаем.

Обратите внимание, чтоio.vertx:vertx-rx-java2 зависит отio.reactivex.rxjava2:rxjava, поэтому нет необходимости явно импортировать какой-либо связанный сRxJava пакет.

Последнюю версию интеграцииVert.x сRxJava можно найти наMaven Central.

3. Настроить

Как и в любом приложении, использующемVert.x,, мы начнем создавать объектvertx, главную точку входа для всех функцийVert.x:

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

Библиотекаvertx-rx-java2 предоставляет два класса:io.vertx.core.Vertx иio.vertx.reactivex.core.Vertx. В то время как первая - обычная точка входа для приложений, однозначно основанных наVert.x, вторая - та, которую мы должны использовать для интеграции сRxJava.

Мы продолжаем определять объекты, которые будем использовать позже:

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

Vert.x ’sFileSystem дает доступ к файловой системе реактивным способом, аVert.x ’sHttpClient делает то же самое дляHTTP.

4. Реактивная цепь

В реактивном контексте легко объединить несколько более простых реактивных операторов, чтобы получить осмысленное вычисление.

Сделаем это для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);

Давайте теперь исследуем, как работает каждый логический фрагмент кода.

5. Названия городов

Первый шаг - прочитать файл, содержащий список названий городов, по одному имени в строке:

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

МетодrxReadFile() реактивно читает файл и возвращаетRxJava ’sSingle<Buffer>. Итак, мы получили интеграцию, которую искали: асинхронностьVert.x в структуре данных изRxJava.

Есть только один файл, поэтому мы получим однократную эмиссиюBuffer с полным содержимым файла. Мы конвертируем этот ввод вRxJava‘sFlowable и сопоставляем строки файла так, чтобы вместо этого былFlowable, который генерирует событие для каждого названия города.

6. Дескриптор города JSON

При наличии названия города следующий шаг - использоватьMetaweather REST API, чтобы получить код идентификатора для этого города. Этот идентификатор будет использоваться для получения времени восхода и захода солнца для города. Продолжим цепочку призывов:

Продолжим цепочку призывов:

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

МетодsearchByCityName() используетHttpClient, которые мы создали на первом шаге, - для вызова службыREST, которая дает идентификатор города. Затем со вторымflatMap(), мы получаемBuffer, содержащий ответ.

Завершим этот шаг написанием телаsearchByCityName():

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 возвращаетRxJava ’sFlowable, который испускает реактивный ответHTTP. Это, в свою очередь, выдает тело ответа, разделенное наBuffers.

Мы создали новый реактивный запрос для правильного URL-адреса, но мы отметили, чтоVert.x requires the HttpClientRequest.end() method to be invoked сигнализирует о том, что запрос может быть отправлен, а также требует как минимум одной подписки, прежде чемend() может быть успешно вызван.

Решением для этого является использованиеRxJava‘sdoOnSubscribe() для вызоваend(), как только потребитель подписывается.

7. Идентификаторы города

Теперь нам просто нужно получить значение свойстваwoeid возвращенного объектаJSON, которое однозначно идентифицирует город с помощью настраиваемого метода:

.map(extractingWoeid())

МетодextractingWoeid() возвращает функцию, которая извлекает идентификатор города изJSON, содержащихся в ответе службыREST:

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

Обратите внимание, что мы можем использовать удобные методыtoJson…(), предоставляемыеBuffer, чтобы быстро получить доступ к нужным нам свойствам.

8. Детали города

Давайте продолжим реактивную цепочку, чтобы получить нужные нам детали изREST API:

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

Давайте подробно рассмотрим методgetDataByPlaceId():

static Flowable getDataByPlaceId(
  HttpClient httpClient, long placeId) {

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

Здесь мы использовали тот же подход, что и на предыдущем шаге. getDataByPlaceId() возвращаетFlowable<HttpClientResponse>. HttpClientResponse, в свою очередь, будет выдавать ответAPI кусками, если он длиннее нескольких байтов.

С помощью методаtoBufferFlowable() мы сокращаем фрагменты ответа в один, чтобы получить доступ к полному объекту JSON:

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

9. Время заката и восхода солнца

Давайте продолжим добавлять в реактивную цепочку, получая интересующую нас информацию из объектаJSON:

.map(toCityAndDayLength())

Напишем методtoCityAndDayLength():

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

Он возвращает функцию, которая отображает информацию, содержащуюся вJSON, для созданияPOJO, которая просто вычисляет время в часах между восходом и заходом солнца.

10. Подписка

Реактивная цепь завершена. Теперь мы можем подписаться на полученныйFlowable с помощью обработчика, который распечатывает выпущенные экземплярыCityAndDayLength или трассировку стека в случае ошибок:

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

Когда мы запускаем приложение, мы можем увидеть такой результат, в зависимости от города, указанного в списке, и даты запуска приложения:

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.

Города могут отображаться в порядке, отличном от указанного в файле, потому что все запросы кHTTP APIвыполняются асинхронно.

11. Заключение

В этой статье мы увидели, как легко смешивать реактивные модулиVert.x с операторами и логическими конструкциями, предоставляемымиRxJava.

Построенная нами реактивная цепочка, пусть и длинная, показала, как довольно легко написать сложный сценарий.

Как всегда, доступен полный исходный кодover on GitHub.