[Из песочницы] Что такое 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 московской биржи:
- получение списка облигаций РСХБ
- получение описания по любой облигации
Для этого создадим новый роутер, опишем все точки и вынесем этот код в отдельный метод:
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 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.На этом, думаю, можно закончить первое знакомство с Vertx. Будем рады увидеть комментарии, ответить на вопросы и получить обратную связь о дальнейшем развитии этого демо-проекта. Если эта тема заинтересует читателей — мы продолжим серию статей и значительно расширим его функционал и сделаем обработку ошибок вызова удаленных сервисов.