Как известно, кто убьет дракона, тот сам становится драконом. 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. Будем рады увидеть комментарии, ответить на вопросы и получить обратную связь о дальнейшем развитии этого демо-проекта. Если эта тема заинтересует читателей – мы продолжим серию статей и значительно расширим его функционал и сделаем обработку ошибок вызова удаленных сервисов.