Vert.x Web API Service Introduction
Vert.x Web API Service
Vert.x 3.6 introduces a new module called vertx-web-api-service
. With the new Web API Services you can easily combine the Vert.x Web Router and the Vert.x OpenAPI Router Factory features with Vert.x Services on Event Bus.
Small recap on OpenAPI and Vert.x Web API Contract
Let’s start from this OpenAPI definition:
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 defined getTransactionsList
, addTransaction
, updateTransaction
and removeTransaction
operations. Now with OpenAPI3RouterFactory
we create a Router
that accepts this various operation requests:
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
provides an easy way to create a specification compliant Router
, but it doesn’t provide a mechanism to decouple the business logic from your operation handlers.
In a typical Vert.x application, when you receive a request to your router, you would forward it to an event bus endpoint that performs some actions and sends the result back to the operation handler.
Vert.x Web API Service simplifies that integration between RouterFactory
and EventBus
with a new code generator. The final result is a loose coupling between the Web Router logic and your business logic.
Let’s get started with Vert.x Web Api Services!
To use vertx-web-api-service
you need to add a couple of dependencies 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 proceed in this order:
- Model the service interface
- Rewrite it to work with Web Api Services
- Implement the service
- Mount the service on the event bus
- Use the router factory to build a router with handlers that connects to our event bus services
Model your service
Let’s say that we want to model a service that manages all operations regarding CRUD transactions. An example interface for this asynchronous service 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 operation, we have some parameters, depending on the operation, and a callback (resultHandler
) that should be called when the operation succeeds or fails.
With Vert.x Service Proxy, you can define an event bus service with a Java interface similar to the one we just saw and then annotate it with @ProxyGen
. This annotation will generate a service handler for the defined service that can be plugged to the event bus with ServiceBinder
. vertx-web-api-service
works in a very similar way: you need to annotate the Java interface with @WebApiServiceGen
and it will generate the service handler for the event bus.
Let’s rewrite the TransactionsManagerService
to work with Web API Service:
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 annotation @WebApiServiceGen
. This annotation will trigger the code generator that generates the event bus handler for this service. Each method has the same two last parameters:
OperationRequest context
: this data object contains the headers and the parameters of the HTTP requestHandler<AsyncResult<OperationResponse>> resultHandler
: this callback accepts anOperationResponse
data object that will encapsulate the body of the result, the status code, the status message and the headers
The generated handler receives only the OperationRequest
data object and extracts from it all operation parameters. For example, when the router receives a request at getTransactionsList
, it sends to TransactionsManagerService
the OperationRequest
containing the RequestParameters
map. From this map, the service generated handler extracts the from
parameter.
Therefore operation parameters names should match method parameter names.
When you want to extract the body you must use body
keyword. For more details, please refer to the documentation.
Implement the service
Now that you have your interface, you can implement the service:
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
documentation to look at various handy methods to create a complete response.
Mount the Service
Now that you have your service interface and implementation, you can mount your service 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 service is up and running, but we need to connect 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 example we added an extension x-vertx-event-bus
to each operation that specifies the address of the service. Using this extension, you only need to call OpenAPI3RouterFactory.mountServicesFromExtensions()
to trigger a scan of all operations and mount all found service addresses. For each operation that contains x-vertx-event-bus
, the Router Factory instantiates an handler that routes the incoming requests to the address you specified.
This is one of the methods you can use to match services with router operation handlers. Check the documentation for all details.
More examples
Check out the complete example in vertx-examples repo.
Thanks you for your time, stay tuned for more updates! And please provide feedback about this new package!