Vert.x Web API Service Introduction

Vert.x 3.6 in­tro­duces a new mod­ule called vertx-web-api-service. With the new Web API Ser­vices you can eas­ily com­bine the Vert.x Web Router and the Vert.x Ope­nAPI Router Fac­tory fea­tures with Vert.x Ser­vices on Event Bus.

Small recap on OpenAPI and Vert.x Web API Contract

Let’s start from this Ope­nAPI de­f­i­n­i­tion:

openapi: 3.0.0
paths:
  /api/transactions:
    get:
      operationId: getTransactionsList
      description: Get transactions list filtered by sender
      x-vertx-event-bus: transactions_manager.myapp
      parameters:
        - name: from
          in: query
          description: Matches exactly the email from
          style: form
          explode: false
          schema:
            type: array
            items:
              type: string
      responses: ...
    post:
      operationId: addTransaction
      x-vertx-event-bus: transactions_manager.myapp
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Transaction"
      responses: ...
  /api/transactions/{transactionId}:
    parameters:
      - name: transactionId
        in: path
        required: true
        schema:
          type: string
    put:
      operationId: updateTransaction
      x-vertx-event-bus: transactions_manager.myapp
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Transaction"
      responses: ...
    delete:
      operationId: removeTransaction
      x-vertx-event-bus: transactions_manager.myapp
      responses: ...
components:
  schemas:
    Transaction: ...
    Error: ...

We de­fined getTransactionsList, addTransaction, updateTransaction and removeTransaction op­er­a­tions. Now with OpenAPI3RouterFactory we cre­ate a Router that ac­cepts this var­i­ous op­er­a­tion re­quests:

OpenAPI3RouterFactory.create(vertx, "src/main/resources/petstore.yaml", ar -> {
  if (ar.succeeded()) {
    // Spec loaded with success
    OpenAPI3RouterFactory routerFactory = ar.result();
    routerFactory.addHandlerByOperationId("getTransactionsList", routingContext -> {
      RequestParameters params = routingContext.get("parsedParameters");
      RequestParameter from = params.queryParameter("from");
      // getTransactionsList business logic
    });
    // add handlers for addTransaction, updateTransaction and removeTransaction
    Router router = routerFactory.getRouter();
  } else {
    // Something went wrong during router factory initialization
    Throwable exception = ar.cause();
    // Log exception, fail verticle deployment ... etc
  }
});

The OpenAPI3RouterFactory pro­vides an easy way to cre­ate a spec­i­fi­ca­tion com­pli­ant Router, but it doesn’t pro­vide a mech­a­nism to de­cou­ple the busi­ness logic from your op­er­a­tion han­dlers.

In a typ­i­cal Vert.x ap­pli­ca­tion, when you re­ceive a re­quest to your router, you would for­ward it to an event bus end­point that per­forms some ac­tions and sends the re­sult back to the op­er­a­tion han­dler.

Vert.x Web API Ser­vice sim­pli­fies that in­te­gra­tion be­tween RouterFactory and EventBus with a new code gen­er­a­tor. The final re­sult is a loose cou­pling be­tween the Web Router logic and your busi­ness logic.

Let’s get started with Vert.x Web Api Services!

To use vertx-web-api-service you need to add a cou­ple of de­pen­den­cies to your project. In a Maven POM file that would be:

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-codegen</artifactId>
  <version>3.6.0</version>
  <classifier>processor</classifier>
</dependency>
<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-web-api-service</artifactId>
  <version>3.6.0</version>
</dependency>

We will pro­ceed in this order:

  1. Model the ser­vice in­ter­face
  2. Rewrite it to work with Web Api Ser­vices
  3. Im­ple­ment the ser­vice
  4. Mount the ser­vice on the event bus
  5. Use the router fac­tory to build a router with han­dlers that con­nects to our event bus ser­vices

Model your service

Let’s say that we want to model a ser­vice that man­ages all op­er­a­tions re­gard­ing CRUD trans­ac­tions. An ex­am­ple in­ter­face for this asyn­chro­nous ser­vice could be:

public interface TransactionsManagerService {
  void getTransactionsList(List<String> from, Handler<AsyncResult<List<Transaction>>> resultHandler);
  void addTransaction(Transaction transaction, Handler<AsyncResult<Transaction>> resultHandler);
  void updateTransaction(String transactionId, Transaction transaction, Handler<AsyncResult<Transaction>> resultHandler);
  void removeTransaction(String transactionId, Handler<AsyncResult<Integer>> resultHandler);
}

For each op­er­a­tion, we have some pa­ra­me­ters, de­pend­ing on the op­er­a­tion, and a call­back (resultHandler) that should be called when the op­er­a­tion suc­ceeds or fails.

With Vert.x Ser­vice Proxy, you can de­fine an event bus ser­vice with a Java in­ter­face sim­i­lar to the one we just saw and then an­no­tate it with @ProxyGen. This an­no­ta­tion will gen­er­ate a ser­vice han­dler for the de­fined ser­vice that can be plugged to the event bus with ServiceBinder. vertx-web-api-service works in a very sim­i­lar way: you need to an­no­tate the Java in­ter­face with @WebApiServiceGen and it will gen­er­ate the ser­vice han­dler for the event bus.

Let’s rewrite the TransactionsManagerService to work with Web API Ser­vice:

import io.vertx.ext.web.api.*;
import io.vertx.ext.web.api.generator.WebApiServiceGen;

@WebApiServiceGen
public interface TransactionsManagerService {
  void getTransactionsList(List<String> from, OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler);
  void addTransaction(Transaction body, OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler);
  void updateTransaction(String transactionId, Transaction body, OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler);
  void removeTransaction(String transactionId, OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler);

  // Factory method to instantiate the implementation
  static TransactionsManagerService create(Vertx vertx) {
    return new TransactionsManagerServiceImpl(vertx);
  }
}

First of all, look at the an­no­ta­tion @WebApiServiceGen. This an­no­ta­tion will trig­ger the code gen­er­a­tor that gen­er­ates the event bus han­dler for this ser­vice. Each method has the same two last pa­ra­me­ters:

  • OperationRequest context: this data ob­ject con­tains the head­ers and the pa­ra­me­ters of the HTTP re­quest
  • Handler<AsyncResult<OperationResponse>> resultHandler: this call­back ac­cepts an OperationResponse data ob­ject that will en­cap­su­late the body of the re­sult, the sta­tus code, the sta­tus mes­sage and the head­ers

The gen­er­ated han­dler re­ceives only the OperationRequest data ob­ject and ex­tracts from it all op­er­a­tion pa­ra­me­ters. For ex­am­ple, when the router re­ceives a re­quest at getTransactionsList, it sends to TransactionsManagerService the OperationRequest con­tain­ing the RequestParameters map. From this map, the ser­vice gen­er­ated han­dler ex­tracts the from pa­ra­me­ter.

There­fore op­er­a­tion pa­ra­me­ters names should match method pa­ra­me­ter names.

When you want to ex­tract the body you must use body key­word. For more de­tails, please refer to the doc­u­men­ta­tion.

Implement the service

Now that you have your in­ter­face, you can im­ple­ment the ser­vice:

public class TransactionsManagerServiceImpl implements TransactionsManagerService {

  private Vertx vertx;

  public TransactionsManagerServiceImpl(Vertx vertx) {  this.vertx = vertx;  }

  @Override
  public void getTransactionsList(List<String> from, OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler){
    // Write your business logic here
    resultHandler.handle(Future.succeededFuture(OperationResult.completedWithJson(resultJson)));
  }

  // Implement other operations

}

Check the OperationResult doc­u­men­ta­tion to look at var­i­ous handy meth­ods to cre­ate a com­plete re­sponse.

Mount the Service

Now that you have your ser­vice in­ter­face and im­ple­men­ta­tion, you can mount your ser­vice with ServiceBinder:

ServiceBinder serviceBinder = new ServiceBinder(vertx);

TransactionsManagerService transactionsManagerService = TransactionsManagerService.create(vertx);
registeredConsumers.add(
  serviceBinder
    .setAddress("transactions_manager.myapp")
    .register(TransactionsManagerService.class, transactionsManagerService)
);

And the Router Factory?

The ser­vice is up and run­ning, but we need to con­nect it to the Router built by OpenAPI3RouterFactory:

OpenAPI3RouterFactory.create(this.vertx, "my_spec.yaml", openAPI3RouterFactoryAsyncResult -> {
  if (openAPI3RouterFactoryAsyncResult.succeeded()) {
    OpenAPI3RouterFactory routerFactory = openAPI3RouterFactoryAsyncResult.result();
    // Mount services on event bus based on extensions
    routerFactory.mountServicesFromExtensions(); // <- Pure magic happens!
    // Generate the router
    Router router = routerFactory.getRouter();
    server = vertx.createHttpServer(new HttpServerOptions().setPort(8080));
    server.requestHandler(router).listen();
    // Initialization completed
  } else {
    // Something went wrong during router factory initialization
  }
});

In our spec ex­am­ple we added an ex­ten­sion x-vertx-event-bus to each op­er­a­tion that spec­i­fies the ad­dress of the ser­vice. Using this ex­ten­sion, you only need to call OpenAPI3RouterFactory.mountServicesFromExtensions() to trig­ger a scan of all op­er­a­tions and mount all found ser­vice ad­dresses. For each op­er­a­tion that con­tains x-vertx-event-bus, the Router Fac­tory in­stan­ti­ates an han­dler that routes the in­com­ing re­quests to the ad­dress you spec­i­fied.

This is one of the meth­ods you can use to match ser­vices with router op­er­a­tion han­dlers. Check the doc­u­men­ta­tion for all de­tails.

More examples

Check out the com­plete ex­am­ple in vertx-​examples repo.

Thanks you for your time, stay tuned for more up­dates! And please pro­vide feed­back about this new pack­age!

Next post

Eclipse Vert.x 3.6.2

We have just released Vert.x 3.6.2, a bug fix release of Vert.x 3.6.x.

Read more
Previous post

HTTP response validation with the Vert.x Web Client

Learn how to use response predicates from the Vert.x Web module to validate HTTP responses and to automatically generate error messages.

Read more
Related posts

Some Rest with Vert.x

This post is part of the Introduction to Vert.x series. Let’s go a bit further this time and develop a CRUD-ish application

Read more

Real-time bidding with Websockets and Vert.x

The expectations of users for interactivity with web applications have changed over the past few years. Users during bidding in auction no longer want to press the refresh button.

Read more

Building services and APIs with AMQP 1.0

Microservices and APIs are everywhere. Everyone talks about them, presentation slides are full of them ... some people are actually even building them.

Read more