Статьи

Vert.x и Fiber: идеальная синергия!

Предположительно, вы уже слышали о  Vert.x , инструментарии для создания приложений Reactive на JVM. Vert.x — полностью асинхронный, легкий, невероятно быстрый, управляемый событиями, и, безусловно, очень многообещающий проект. Некоторые из вас, возможно, слышали о волокнах — если вы не знакомы с ними, проверьте  здесь  и  здесь . В дополнение к неблокирующему и управляемому событиями инструментарию Vert.x мы можем воспользоваться Vertx-Sync как компонентом, с помощью которого мы можем легко вырваться из известного ада обратного вызова при использовании неблокирующих API. Vertx-Sync позволяет выполнять асинхронные операции и получать события синхронно, но без блокировки потоков ядра. В результате, даже если он синхронный, он ничтожно менее производительный.  VertX-Sync использует  Quasar  для обеспечения своих волокон.

В этой статье я покажу вам, как:

  1. Создайте простой проект Vert.x
  2. Написать простой и краткий код с помощью Vertx-Sync.  
  3. Создайте простой REST API через Vertx-Web.
  4. Получить некоторый веб-контент с помощью HTTP-клиента.
  5. Используйте MongoDB в качестве примера для использования базы данных с Vert.x.

Вы можете отправиться на GitHub, чтобы следовать  здесь . Ради этого блога нам понадобятся:

  1. JDK 1.8
  2. специалист
  3. MongoDB

Установить и настроить MongoDB

Загрузите последнюю версию MongoDB и разархивируйте ее в свой локальный каталог. Вы можете скачать его  здесь . Затем создайте следующий каталог / data / db внутри каталога, куда распаковывается MongoDB.

Чтобы запустить сервер, перейдите в {mongo_directory} / bin и запустите  mongod .

Теперь пришло время запустить клиент Монго. Перейдите в {mongo_directory} / bin и запустите  mongo  .

Создайте базу данных testDB и коллекцию Entities:

use testDB;
db.createCollection('Entities');

Создать простой проект Vert.x

Пришло время создать наш проект Vert.x. Давай сделаем это! Наша отправная точка проекта:

import io.vertx.core.Vertx;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;

public class Launcher {
    private static final Logger logger = LoggerFactory.getLogger(MainVerticle.class);

    public static void main(String[] args) {
        Vertx.vertx().deployVerticle(MainVerticle.class.getName(), h -> {
            if (h.succeeded()) {
                logger.info("Success: {0}", h.result());
            } else {
                logger.error("Something went wrong: {0}", h.cause());
            }
        });

И наша вертикаль:

import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.ErrorHandler;

import java.util.List;


public class MainVerticle extends SyncVerticle {

    private static final Logger 
    logger = LoggerFactory.getLogger(MainVerticle.class);
    private static final String COLLECTION_NAME= "Entities";
    private WebClient webClient;
    private MongoClient mongoClient;

    @Override
    @Suspendable
    public void start(Future<Void> startFuture) throws Exception {
        super.start(startFuture);
        HttpServer server = vertx.createHttpServer();
        Router router = Router.router(vertx);
        // enable BodyHandler globally for easiness of body accessing
        router.route().handler(BodyHandler.create()).failureHandler(ErrorHandler.create());
        router.route(HttpMethod.GET, 
        "/getWebContent").handler(Sync.fiberHandler(this::getWebContent));
        router.route(HttpMethod.GET, 
        "/entities").handler(Sync.fiberHandler(this::getAllEntities));
        router.route(HttpMethod.GET, 
        "/entities/:id").handler(Sync.fiberHandler(this::getEntityById));
        router.route(HttpMethod.PUT, 
        "/entities").handler(Sync.fiberHandler(this::saveNewEntity));
        // HttpServer will be automatically shared if port matches
        server.requestHandler(router::accept).listen(8089);
        webClient = WebClient.create(vertx, new WebClientOptions().setSsl(true));
        mongoClient = MongoClient.createShared
       (vertx, new JsonObject().put("connection_string", "mongodb://127.0.0.1:27017/testDb"));
    }

    @Suspendable
    private void saveNewEntity(RoutingContext routingContext){
        final String response = Sync.awaitResult
        (h ->  mongoClient.save(COLLECTION_NAME, routingContext.getBodyAsJson(), h));
        routingContext.response().end(response);
    }

    @Suspendable
    private void getAllEntities(RoutingContext routingContext){
        final List<JsonObject> entities = Sync.awaitResult
        (h ->  mongoClient.find(COLLECTION_NAME, new JsonObject(), h));
        routingContext.response().end(Json.encodePrettily(entities));
    }

    @Suspendable
    private void getEntityById(RoutingContext routingContext){
        final JsonObject query = new JsonObject()
                .put("_id", routingContext.pathParam("id"));
        final List<JsonObject> entity = Sync.awaitResult
        (h ->  mongoClient.find(COLLECTION_NAME, query, h));
        routingContext.response()
                .end(Json.encodePrettily(entity));
    }

    @Suspendable
    private void getWebContent(RoutingContext routingContext){
        final HttpResponse<Buffer> response = Sync.awaitResult
        (h -> webClient.getAbs("https://www.google.com").send(h));
        final String responseContent = response.bodyAsString("UTF-8");
        logger.info("Result of Http request: {0}", responseContent);
        routingContext.response()
                .putHeader(HttpHeaderNames.CONTENT_TYPE, "text/html")
                .end(responseContent);

  1. Класс Launcher предназначен только для запуска нашей программы и развертывания нашей MainVerticle.
  2. Необходимо учитывать, что наша MainVerticle расширяет io.vertx.ext.sync.SyncVerticle. Если он не развернут как экземпляр SyncVerticle, волокна не будут работать.
  3. Создайте HttpServer и маршрутизатор для обработки нашего REST API (выполнив следующие три шага):

    1. GET  http: // localhost: 8089 / getWebContent  — запросить « https://www.google.com » только для тестирования асинхронного HTTP-клиента.
    2. ПОЛУЧИТЕ  http: // localhost: 8089 / entity
    3. GET  http: // localhost: 8089 / entity /: id   -: id — это параметр пути для поиска какой-либо сущности
    4. PUT  http: // localhost: 8089 / entity  — создать новую запись объекта. Примените JSON к полезной нагрузке, например: {«name»: «Charlie Harper»}
  4. Создайте WebClient и MongoClient и используйте их асинхронные API синхронно через Fibers.
  5. Очень важно использовать Sync.fiberHandler (), когда нам нужно использовать любые обработчики. Для использования Fibers в обычном обработчике, например в обработчике запросов HTTP-сервера, мы должны сначала преобразовать обычный обработчик в обработчик волокна.
  6. Получение ответа от асинхронных API, которые возвращают AsyncResult <T> в качестве обратного вызова, очень просто с Sync.awaitResult (). Ниже приведена некоторая скопированная документация Vertx по этому поводу:

«Многие асинхронные операции в Vert.x-land принимают Handler <AsyncResult <T >> в качестве последнего аргумента. Примером может служить выполнение поиска с использованием клиента Vert.x Mongo или отправка сообщения шины событий и получение ответа. Vertx -sync позволяет синхронно получать результат однократной асинхронной операции. Это выполняется с помощью метода Sync.awaitResult. «

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

final List<JsonObject> entity = Sync.awaitResult
(h ->  mongoClient.find(COLLECTION_NAME, query, h));
final HttpResponse<Buffer> 
response = Sync.awaitResult
(h -> webClient.getAbs("https://www.google.com").send(h));

Самое главное, чтобы инструмент нашего байт-кода. Vert.x использует Quasar, а их реализация Fiber использует инструментарий байт-кода. Нам не нужно делать ничего особенного, просто нужно запустить JVM с javaagent в качестве опции VM. Например:

javaagent: /path/to/quasar/core/quasar-core.jar.

Для этого мы используем «exec-maven-plugin». Вот содержание pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.idle.fibers</groupId>
    <artifactId>vertx-sync-fibers</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <vertx.version>3.4.1</vertx.version>
        <main.verticle>org.idle.easy.fibers.MainVerticle</main.verticle>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.vertx</groupId>
                <artifactId>vertx-dependencies</artifactId>
                <version>${vertx.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-web</artifactId>
            <version>${vertx.version}</version>
        </dependency>
        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-web-client</artifactId>
            <version>${vertx.version}</version>
        </dependency>
        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-auth-oauth2</artifactId>
            <version>${vertx.version}</version>
        </dependency>
        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-sync</artifactId>
            <version>${vertx.version}</version>
        </dependency>
        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-mongo-client</artifactId>
            <version>${vertx.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-clean-plugin</artifactId>
                <version>3.0.0</version>
                <configuration>
                    <filesets>
                        <fileset>
                            <directory>${project.basedir}/src/main/generated</directory>
                        </fileset>
                    </filesets>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-deploy-plugin</artifactId>
                <version>2.8.2</version>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <configuration>
                    <mainClass>org.idle.easy.fibers.Launcher</mainClass>
                    <workingDirectory>target/classes</workingDirectory>
                    <executable>java</executable>
                    <arguments>
                        <argument>-javaagent:${co.paralleluniverse:quasar-core:jar}</argument>
                    </arguments>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Наконец, чтобы запустить нашу программу, просто наберите: mvn clean package exec: java

И это все!