company_banner

Что такое Vertx, и почему он подходит для РСХБ

Как известно, кто убьет дракона, тот сам становится драконом. Spring, как фреймворк общего назначения, был очень хорош на фоне java EE 10 лет назад. Но сейчас стал очень монструозным и тяжелым на подьем. Сегодня рассмотрим Vertx как фреймворк-основу для создания микросервисов.


Что такое Vertx?



По сути это легковесный фреймворк для создания событийно-ориентированных асинхронных приложений любой сложности: от сетевой утилиты до современного веб приложения или микросервисов; от игр до банков и все, что между этим. Среди компаний, которые его используют, числятся такие монстры как RedHat и Bosch.


Минимально возможной единицей деплоя является verticle(вертикл). Это некий аналог микросервиса. Вертиклы могут быть запущены в разных java машинах на разных физических хостах и общаться между собой через встроенную распределенную шину данных (event bus).


Vertx является фреймворком-полиглотом – это значит, что каждый вертикл может быть написан на своем языке программирования. Список поддерживаемых языков: Java, Kotlin, JavaScript, Groovy, Ruby, Scala.


Установка вертиклов возможна в уже запущенный Vertx с разных источников, таких как локальная папка, git или maven репозиторий без остановки или рестарта уже запущенных вертиклов.


Больше технической информации можно найти на http://vertx.io. Также в разделе http://vertx.io/docs помимо самой документации доступны несколько книг и ссылки на примеры кода на всех поддерживаемых языках.


Почему Vertx?


Итак, мы выяснили, «что же ты такое». Теперь давайте попробуем понять, почему именно Vertx?


Инфраструктура внутренних приложений банка очень разнородна. Для нашей организации требуется фреймворк с большим количеством готовых плагинов, компонентов, интеграций с другими системами, а также готовыми системами мониторинга и разворачивания в гетерогенной инфраструктуре. Vertx можно легко использовать как консольное приложение, так и в виде кластера внутри Kubernetes с функционалом высокой доступности для каждого verticle. Есть готовые образы docker и поддержка разных систем авторизации и хранения конфигураций.


Немаловажным является размер финального приложения, потребляемые ресурсы и время старта. Vetrtx позволяет писать приложения с высокой степенью параллельной обработки данных на не очень мощном железе. Также архитектура Vertx позволяет писать многопоточные приложения — просто.


Тестовое приложение


Для примера давайте напишем небольшой микросервис с минимальным rest api и метриками для Prometheus также доступными через web socket.


Функционал максимально простой (2 rest endpoint). Мы принимаем rest запрос, далее переадресовываем на сервер Московской биржи и формируем ответ клиенту. Также подключим micrometer метрики и будем публиковать их по встроенной шине данных в web socket.
Итак, начнем.
Идем на http://start.vertx.io, создаем проект с 4 модулями:


  • Vert.x Web
  • Vert.x Web Client
  • Metrics using Micrometer
  • SockJS Service Proxies

и загружаем к себе на машину – мы получили полностью готовый проект.
Если выполним в папке с пректом консольную команду:


./mvnw clean compile exec:java

то на http://localhost:8888 сможем увидеть приветствие: Hello from Vert.x!


Из коробки мы имеем уже готовый веб-сервер, шину данных и возможность поднять данный проект в кластере. И это при размере финального «толстого» jar в 7,5 мегабайт!


Ладно, хватит восхвалений, перейдем к написанию нашего микросервиса.


Шаг 1. Обработка HTTP запросов


В Vertx маршрутизация http запросов осуществляется с помощью Router. Назначить обработчики запросов очень просто:


Router router = Router.router(vertx); //создаем роутер
router.get("/hello").handler(rc -> { //назначаем обработчик для GET запросов для пути /hello
  rc.response()
    .putHeader(HttpHeaders.CONTENT_TYPE, "text/plain")
    .end("Hello from Vert.x!");
});

vertx.createHttpServer()
  .requestHandler(router) //создаем http сервер и назначаем роутер обработчиком запросов
  .listen(8888, http -> {....

Теперь то же приветствие доступно по пути http://localhost:8888/hello.


Шаг 2. Пишем REST API


API будет состоять из 2-х точек с вызовом другого удаленного api московской биржи:


  1. получение списка облигаций РСХБ
  2. получение описания по любой облигации

Для этого создадим новый роутер, опишем все точки и вынесем этот код в отдельный метод:


private Router createRestRouter(WebClient webClient) {
  Router restApi = Router.router(vertx);
  restApi.get("/rshb_bonds").handler(rc -> {
    webClient
      .get(80, "iss.moex.com", "/iss/securities.json")
      .addQueryParam("q", "РСХБ")
      .send(response -> {
        rc.response()
          .putHeader(HttpHeaders.CONTENT_TYPE, "application/json")
          .end(processMoexBondsRequest(response.result().bodyAsJsonObject()).encodePrettily());
      });
  });
  restApi.get("/rshb_bonds/:bondId").handler(rc -> { //часть url используется как параметр
    String bondId = rc.request().getParam("bondId");
      webClient
        .get("/iss/securities/"+ bondId +".json")
        .send(response -> {
          rc.response()
            .putHeader(HttpHeaders.CONTENT_TYPE, "application/json")
            .end(
                processMoexBondDescriptionRequest(response.result().bodyAsJsonObject())
            );
        });
    });
  return restApi;
}

Функции processMoexBondsRequest, processMoexBondDescriptionRequest можно посмотреть на github. Их реализация опущена для читаемости листинга. Так же пока опустим обработку ошибок.


А так же прикрепим роут c нашим api в главный роут с префиксом “/rest/api/v1”.


router.mountSubRouter("/rest/api/v1/", createRestRouter(webClient));

Самые внимательные читатели уже заметили, что для вызовов удаленного http rest сервиса используется встроенный http клиент. Это тоже модуль Vertx, скрывающий за собой целый пласт функционала, такого как: ssl, пул соединений, таймауты соединений и т.д. Создается обьект в едином стиле всего фреймворка:


WebClientOptions webClientOptions = new WebClientOptions();
webClientOptions //значения по умолчанию, это позволит не проставлять их при каждом вызове
    .setDefaultPort(80)
    .setDefaultHost("iss.moex.com");
WebClient webClient = WebClient.create(vertx, webClientOptions);

ВСЕ! мы создали api, который можно проверить по следующим url:
http://localhost:8888/rest/api/v1/rshb_bonds
http://localhost:8888/rest/api/v1/rshb_bonds/RU000A101WQ2


Шаг 3. Получение метрик приложения


Для получения метрик нашего приложения, мы используем уже включенный в состав приложения модуль Micrometer metrics, а для того, чтобы он заработал, нужно указать эту настройку при запуске Vertx. Но в данный момент мы задействуем для запуска стандартный класс io.vertx.core.Launcher, который по умолчанию использует пустые параметры запуска. Ничего страшного, расширим его и укажем наш класс, в качестве стартового в pom.xml


public class LauncherWithMetrics extends Launcher {
  public static void main(String[] args) {
    new LauncherWithMetrics().dispatch(args); // необходимо для корректной работы лаунчера
  }

  @Override
  public void beforeStartingVertx(VertxOptions options) {
    options.setMetricsOptions(
      new MicrometerMetricsOptions()
        .setPrometheusOptions(new VertxPrometheusOptions().setEnabled(true))
        .setEnabled(true));
  }
}

Осталось добавить в роутер точку /metrics


router.route("/metrics").handler(PrometheusScrapingHandler.create());

Готово. Теперь мы можем подключить Prometheus сервер к нашему сервису и собирать метрики. И все, что для этого понадобилось – пара десятков строк кода.


Шаг 4. Демонстрация метрик в браузере


Далее представим, что мы хотим показывать эти метрики в режиме реального времени в браузере. Для этого добавим еще один вертикл, который будет публиковать метрики каждую секунду на шину:


public class MetricsBusPublisher extends AbstractVerticle {
  @Override
  public void start(Promise<Void> startPromise) {
    MetricsService metricsService = MetricsService.create(vertx);
    vertx.setPeriodic(
      1000,
      h ->
        vertx.eventBus().publish("metrics", metricsService.getMetricsSnapshot().encode())
    );
    startPromise.complete();
  }
}

и обработчик в роутер для трансляции содержимого шины в веб сокет:


SockJSBridgeOptions opts = new SockJSBridgeOptions()
  .addOutboundPermitted(new PermittedOptions()
    .setAddress("metrics")); // В целях безопасности можно указать, какие очереди доступны для трансляции, а какие могут принимать сообщения из вебсокета. Фильтры возможны на основе регулярных выражений.
router.mountSubRouter("/eventbus", SockJSHandler.create(vertx).bridge(opts));

Теперь можно запустить webSocket.html из корня проекта и посмотреть метрики в реальном времени.


Где найти проект


Проект доступен на github:
https://github.com/RshbExample/VertxFirstSteps.git


В заключение


На этом, думаю, можно закончить первое знакомство с Vertx. Будем рады увидеть комментарии, ответить на вопросы и получить обратную связь о дальнейшем развитии этого демо-проекта. Если эта тема заинтересует читателей – мы продолжим серию статей и значительно расширим его функционал и сделаем обработку ошибок вызова удаленных сервисов.

Комментарии 6

    +1
    Риторический вопрос — А чего так про Spring?
    А что у Vert.x с масштабируемостью и стабильностью?
      0
      Да поди готовить не умеют? Как-бы очевидный ответ на «А теперь Spring стал монструозный» выглядит просто — возьмите прошлую версию, она никуда не делась. И это вполне возможно решит ваши проблемы.
        0
        Масштабируемость заложена в базовой функциональности. Возможен запуск на множестве джава машин с объединением в кластер. Насчет стабильности: в кластере из коробки доступен функционал «high availability» для любого задеплоенного вертикла. Если вертикл «упадет», он будет поднят на другом инстансе Vertx кластера.
        Более подробно все это рассмотрим в дальнейших статьях цикла.
        0

        Как то год проработал на проекте с микросервисным vert.x и вспоминаю, как страшный сон. По мне дак по простоте он и рядом не стоит со спрингом. Очень много ручной работы в контроллерах и там где не нужно. Даже у вас в статье простейший пример hello world выглядит достаточно громоздко и там где у спринга ставится одна аннотация на vertx приходится городить огород.

          0
          Это пример с коллбэками, планирую и дальше увеличить их количество, чтобы потом от них избавиться. С аннотациями другая проблема, они имеют свойство расползаться по коду и множиться на определенных методах в несколько этажей. Иногда возникают проблемы от того, что их функционал начинает давать побочные эффекты друг на друга. Никто ведь не тестирует все возможные варианты наслоения оберток.
            0
            так если вы встречали эти наслоения, может стоит о них сообщить, чтобы улучшить продукт?

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

        Самое читаемое