Criando APIs Rest com Vert.x

O Vert.x é um lightweight que possibilita o desenvolvimento de aplicações reativas baseadas em JVM. Dentro da gama de módulos, nesse post vamos conhecer o módulo web do Vert.x para criação de APIs Rest.

O módulo web foi inspirado no Express (NodeJs) e Sinatra (Ruby), onde foi desenhado para ser executado independente, sem a necessidade de containers.

Exemplo

O exemplo a ser utilizado Vert.x é a construção de uma API Rest que retorna uma lista de empresas.

Dependências

As dependências do Vert.x são separadas por módulos: web, web-client, kafka-client, etc, com isso, no nosso exemplo vamos utilizar a dependência vertx-web, que possibilita a construção de servers Http.

<dependencies>
   <dependency>
        <groupId>io.vertx</groupId>
        <artifactId>vertx-web</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-stack-depchain</artifactId>
            <version>3.8.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Observação: Não é necessário adicionar a dependência do vertx-core, pois o vertx-web já importa o core.

Iniciando uma aplicação Vert.x

Aplicações Vert.x podem ser executadas basicamente de duas formas, executando o próprio Verticle e utilizando um Launcher do Vert.x para execução ou iniciando o Vert.x em um método main, que é a forma que vamos utilizar no exemplo.

Com isso, vamos criar uma classe com método main, criar uma instância do Vert.x e fazer deploy da classe HTTPServerVerticle, que é onde vamos implementar o endpoint de empresas.

import br.com.emmanuelneri.vertx.http.verticle.HTTPServerVerticle;
import io.vertx.core.Vertx;

public class Application {

    public static void main(String[] args) {
        final Vertx vertx = Vertx.vertx();

        vertx.deployVerticle(new HTTPServerVerticle());
    }

}
  • Vertx.vertx(): Cria uma instância do Vert.x, nesse momento a aplicação está sendo iniciada;
  • vertx.deployVerticle: Implanta no contexto do Vert.x a instância da classe passada por parâmetro, que deve herdar de Verticle.

Observação: Poderia ter a implementação do HttpServer dentro do próprio main e não precisarmos do deploy do verticle, porém, em termos de organização e isolamento, é uma boa prática deixar o método main apenas com responsabilidade de iniciar o Vert.x e fazer deploy dos verticles.

O Vert.x trabalha baseado “Verticle”, que são trechos de códigos estruturadas em classes dentro do contexto, assim, proporcionando trabalharmos mais isolado na construção das nossas funcionalidades, onde cada “pedaço” da aplicação poderá ser implantada separada no contexto da aplicaçnao.

Após o deploy da classe HTTPServerVerticle, precisamos implementar nosso “Verticle” para disponibilizar APIs na nossa aplicação, como hello world, vamos inicialmente disponibilizando um endpoint GET que retorna apenas uma mensagem estática, demonstrado no exemplo abaixo:

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.http.HttpServer;
import io.vertx.ext.web.Router;

public class HTTPServerVerticle extends AbstractVerticle {

    @Override
    public void start(final Future<Void> startFuture) {
        final HttpServer httpServer = vertx.createHttpServer();

        final Router router =  Router.router(vertx);
        router.get("/").handler(routingContext -> routingContext.response()
               .end("Welcome to Vert.x HTTP Server"));

        httpServer.requestHandler(router)
                .listen(8080, listenHandler -> {
                     if(listenHandler.failed()) {
                        LOGGER.error("HTTP Server error", listenHandler.cause());
                        return;
                    }
                    LOGGER.info("HTTP Server started on port 8080");
                });

    }
}
  • vertx.createHttpServer(): Inicia uma instância do HTTP server disponibilizado pelo Vert.x;
  • Router.router(vertx): O HTTP server é baseado em rotas, com isso precisamos criar uma instância de rotas para adicionar no server;
  • router.get(“/”): Define uma rota no caminho “/”;
  • router.get(“/”).handler: A rota dever ter um handler, que será o evento gerado quando executado a rota;
  • routingContext -> routingContext.response().end(): O handler da rota recebe o parâmetro routingContext, que possibilita capturar dados do request e alterar informações do response, como no exemplo, que é adicionado o texto fixo no retorno do response;
  • httpServer.requestHandler(router): Adiciona as rotas configurados (router) no server;
  • httpServer.listen(8080): Inicia o server na porta 8080.

Agora no nosso exemplo, implementando uma API que retorna uma lista de empresas, adicionamos uma nova rota retornando uma lista de empresas no response, onde essa lista estará no formato JSON

router.get("/companies").handler(routingContext -> {
    final List<Company> companies = new ArrayList<>();
    companies.add(new Company(1L, "Company 1"));
    companies.add(new Company(2L, "Company 2"));

    routingContext.response().end(Json.encode(companies));
});

A construção da rota é similar ao exemplo anterior, mudando apenas o conteúdo do método end do response, onde a lista de companies é convertida para JSON.

  • Json.encode: Transforma qualquer objeto em JSON.

Um ponto interessante dessa implementação, é que o Vert.x é não bloqueante, então a execução do handler da rota (onde é construído a lista de empresas) é executado assíncrono do processo inicial e quando finalizado notifica para retorno da rota, dessa forma, o vert.x consegue gerenciar melhor o maior número de requisições.

Observação: A classe Json do Vert.x utiliza object mapper da lib Jackson para realizar a serialização e desserialização de objetos.

Podemos e devemos adicionar tratamento de erros nas rotas, pois, por padrão, o Vert.x apenas retorna um status 500 e não detalha o problema que ocorre dentro de uma rota, com isso, devemos implementar o failureHandler nas rotas, como no exemplo abaixo.

router.get("/companies").handler(routingContext -> {
...
}).failureHandler(errorRoutingContext -> {
    LOGGER.error("Route error", errorRoutingContext.failure());
    errorRoutingContext.response()
        .setStatusCode(errorRoutingContext.statusCode())
        .end();
});
  • errorRoutingContext.failure(): Retorna a excessão lançada durante o handler do evento;
  • failureHandler: Handler acionado em caso de falhas;
  • setStatusCode: Retornando o mesmo statusCode capturando pelo failureHandler.

Conclusão

O Vert.x é uma solução bastante leve comparado com outros framework, que possibilita a criação de APIs REST facilmente com a utilização do modulo web, onde implementamos execuções assíncronas baseadas nos princípios de programação reativa. Com isso, o gerenciamento de recurso das aplicações são melhores desempenhados, visando atender um maior número de requisições com menos recurso.

Extra

Podemos extrair as implementações dos handlers das rotas para deixar nossos verticles mais limpo, como no código abaixo, onde as implementações foram movidas para classes específicas como: FailureHandler, DefaultRouting e CompanyRouting.

import br.com.emmanuelneri.vertx.http.web.CompanyRouting;
import br.com.emmanuelneri.vertx.http.web.DefaultRouting;
import br.com.emmanuelneri.vertx.http.web.FailureHandler;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.http.HttpServer;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.ErrorHandler;


public class HTTPServerVerticle extends AbstractVerticle {

    private final Logger LOGGER = LoggerFactory.getLogger(HTTPServerVerticle.class);

    @Override
    public void start(final Future<Void> startFuture) {
        final HttpServer httpServer = vertx.createHttpServer();

        final Router router =  Router.router(vertx);
        final FailureHandler failureHandler = new FailureHandler();
        router.get("/").handler(DefaultRouting.welcome()).failureHandler(failureHandler);
        router.get("/companies").handler(CompanyRouting.findAll()).failureHandler(failureHandler);

        httpServer.requestHandler(router)
                .listen(8080, listenHandler -> {
                    if(listenHandler.failed()) {
                        LOGGER.error("HTTP Server error", listenHandler.cause());
                        return;
                    }
                    LOGGER.info("HTTP Server started on port 8080");
                });

    }
}

O código fonte dos exemplos estão disponíveis no github.

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.