«1. Обзор

В этой статье мы обсудим Vert.x, охватим его основные концепции и создадим с его помощью простой веб-сервис RESTfull.

Мы начнем с рассмотрения основных концепций набора инструментов, постепенно перейдем к HTTP-серверу, а затем создадим службу RESTfull.

2. О Vert.x

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

Реактивное программирование — это парадигма программирования, связанная с асинхронными потоками, которые реагируют на любые изменения или события.

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

Мы называем его полиглотом из-за поддержки нескольких языков JVM и других языков, таких как Java, Groovy, Ruby, Python и JavaScript.

3. Настройка

Чтобы использовать Vert.x, нам нужно добавить зависимость Maven:

<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-core</artifactId>
    <version>3.4.1</version>
</dependency>

Последнюю версию зависимости можно найти здесь.

3. Verticles

Verticles — это фрагменты кода, которые выполняет движок Vert.x. Инструментарий предоставляет нам множество абстрактных классов вершин, которые можно расширять и реализовывать по своему усмотрению.

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

Чтобы создать вертикул в JAVA, класс должен реализовать интерфейс io.vertx.core.Verticle или любой из его подклассов.

4. Шина событий

Это нервная система любого приложения Vert.x.

Будучи реактивными, вершины остаются бездействующими, пока не получат сообщение или событие. Вертиклы взаимодействуют друг с другом через шину событий. Сообщение может быть чем угодно, от строки до сложного объекта.

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

5. Простое приложение Vert.x

Давайте создадим простое приложение с verticle и развернем его, используя экземпляр vertx. Чтобы создать нашу ветку, мы расширим

Чтобы создать нашу ветку, мы расширим класс io.vertx.core.AbstractVerticle и переопределим метод start():

public class HelloVerticle extends AbstractVerticle {

    @Override
    public void start(Future<Void> future) {
        LOGGER.info("Welcome to Vertx");
    }
}

Метод start() будет вызываться экземпляром вершины при развертывании вершины. Метод принимает io.vertx.core.Future в качестве параметра, который можно использовать для определения статуса асинхронного развертывания вертикали.

Теперь развернем вертикул:

public static void main(String[] args) {
    Vertx vertx = Vertx.vertx();
    vertx.deployVerticle(new HelloVerticle());
}

Аналогичным образом мы можем переопределить метод stop() из класса AbstractVerticle, который будет вызываться при завершении работы вертикула:

@Override
public void stop() {
    LOGGER.info("Shutting down application");
}

6. HTTP-сервер

Теперь давайте запустим HTTP-сервер с помощью вертикла:

@Override
public void start(Future<Void> future) {
    vertx.createHttpServer()
      .requestHandler(r -> r.response().end("Welcome to Vert.x Intro");
      })
      .listen(config().getInteger("http.port", 9090), 
        result -> {
          if (result.succeeded()) {
              future.complete();
          } else {
              future.fail(result.cause());
          }
      });
}

Мы переопределили метод start() для создания HTTP-сервера и прикрепили к нему обработчик запросов. Метод requestHandler() вызывается каждый раз, когда сервер получает запрос.

Наконец, сервер привязывается к порту, и методу listen() передается обработчик AsyncResult\u003cHttpServer\u003e независимо от того, удалось ли установить соединение или запустить сервер с помощью future.complete() или future.fail( ) в случае каких-либо ошибок.

Обратите внимание: метод config.getInteger() считывает значение для конфигурации HTTP-порта, которое загружается из внешнего файла conf.json.

Давайте протестируем наш сервер:

@Test
public void whenReceivedResponse_thenSuccess(TestContext testContext) {
    Async async = testContext.async();

    vertx.createHttpClient()
      .getNow(port, "localhost", "/", response -> {
        response.handler(responseBody -> {
          testContext.assertTrue(responseBody.toString().contains("Hello"));
          async.complete();
        });
      });
}

Для теста воспользуемся vertx-unit вместе с JUnit.:

<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-unit</artifactId>
    <version>3.4.1</version>
    <scope>test</scope>
</dependency>

Мы можем получить последнюю версию здесь.

Вертикальное развертывание и экземпляр вершины в методе setup() модульного теста:

@Before
public void setup(TestContext testContext) {
    vertx = Vertx.vertx();

    vertx.deployVerticle(SimpleServerVerticle.class.getName(), 
      testContext.asyncAssertSuccess());
}

Точно так же экземпляр вершины закрывается в методе @AfterClass tearDown():

@After
public void tearDown(TestContext testContext) {
    vertx.close(testContext.asyncAssertSuccess());
}

~~ ~ Обратите внимание, что метод setup() @BeforeClass принимает аргумент TestContext. Это помогает контролировать и тестировать асинхронное поведение теста. Например, развертывание вертикалей является асинхронным, поэтому в основном мы не можем ничего протестировать, если оно не развернуто правильно.

«У нас есть второй параметр метода deployVerticle(), testContext.asyncAssertSuccess(). Это используется, чтобы узнать, правильно ли развернут сервер или произошли какие-либо сбои. Он ожидает вызова future.complete() или future.fail() в вершине сервера. В случае сбоя он не проходит тест.

7. RESTful WebService

Мы создали HTTP-сервер, давайте теперь используем его для размещения RESTfull WebService. Для этого нам понадобится еще один модуль Vert.x под названием vertx-web. Это дает множество дополнительных возможностей для веб-разработки поверх vertx-core.

Давайте добавим зависимость в наш pom.xml:

<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-web</artifactId>
    <version>3.4.1</version>
</dependency>

Мы можем найти последнюю версию здесь.

7.1. Маршрутизатор и маршруты

Давайте создадим маршрутизатор для нашего веб-сервиса. Этот маршрутизатор использует простой метод GET и метод-обработчик getArtilces():

Router router = Router.router(vertx);
router.get("/api/baeldung/articles/article/:id")
  .handler(this::getArticles);

Метод getArticle() — это простой метод, который возвращает новый объект статьи:

private void getArticles(RoutingContext routingContext) {
    String articleId = routingContext.request()
      .getParam("id");
    Article article = new Article(articleId, 
      "This is an intro to vertx", "baeldung", "01-02-2017", 1578);

    routingContext.response()
      .putHeader("content-type", "application/json")
      .setStatusCode(200)
      .end(Json.encodePrettily(article));
}

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

В нашем случае обработчик вызывает метод getArticle(). Он получает объект routingContext в качестве аргумента. Получает идентификатор параметра пути и создает с ним объект статьи.

В последней части метода давайте вызовем метод response() для объекта routingContext и поместим заголовки, установим код ответа HTTP и закончим ответ, используя объект статьи в формате JSON.

7.2. Добавление маршрутизатора к серверу

Теперь давайте добавим маршрутизатор, созданный в предыдущем разделе, к HTTP-серверу:

vertx.createHttpServer()
  .requestHandler(router::accept)
  .listen(config().getInteger("http.port", 8080), 
    result -> {
      if (result.succeeded()) {
          future.complete();
      } else {
          future.fail(result.cause());
      }
});

Обратите внимание, что мы добавили requestHandler(router::accept) на сервер. Это указывает серверу вызывать метод accept() объекта маршрутизатора при получении любого запроса.

Теперь давайте протестируем наш WebService:

@Test
public void givenId_whenReceivedArticle_thenSuccess(TestContext testContext) {
    Async async = testContext.async();

    vertx.createHttpClient()
      .getNow(8080, "localhost", "/api/baeldung/articles/article/12345", 
        response -> {
            response.handler(responseBody -> {
            testContext.assertTrue(
              responseBody.toString().contains("\"id\" : \"12345\""));
            async.complete();
        });
      });
}

8. Упаковка приложения Vert.x

Чтобы упаковать приложение в виде развертываемого Java-архива (.jar), воспользуемся подключаемым модулем Maven Shade и конфигурациями в теге выполнения :

<configuration>
    <transformers>
        <transformer 
          implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
            <manifestEntries>
                <Main-Class>io.vertx.core.Starter</Main-Class>
                <Main-Verticle>com.baeldung.SimpleServerVerticle</Main-Verticle>
            </manifestEntries>
        </transformer>
    </transformers>
    <artifactSet />
    <outputFile>
        ${project.build.directory}/${project.artifactId}-${project.version}-app.jar
    </outputFile>
</configuration>

В manifestEntries Main-Verticle указывает начальную точку приложения, а Main-Class — это класс Vert.x, который создает экземпляр vertx и развертывает Main-Verticle.

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

В этой вводной статье мы обсудили инструментарий Vert.x и его основные концепции. Увидел, как создать HTTP-сервер с помощью Vert.x, а также RESTFull WebService, и показал, как их тестировать с помощью vertx-unit.

Наконец-то упаковал приложение в исполняемый jar-файл.

Полная реализация фрагментов кода доступна на GitHub.