Vert.x Web API Service
Vert.x Web API Service helps you routing the API requests incoming from a Vert.x Web Router built with Vert.x Web API Contract Router Factory to event bus. Event Bus provides itself important features like load balancing and distribution of requests across different Vert.x instances. We encourage you to give a look at Event Bus documentation
It’s based on same concept of Vert.x service proxies.
Using Vert.x API Service
To use Vert.x API Service, add the following dependency to the dependencies section of your build descriptor:
-
Maven (in your
pom.xml
):
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-codegen</artifactId>
<version>3.9.16</version>
<classifier>processor</classifier>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-api-service</artifactId>
<version>3.9.16</version>
</dependency>
-
Gradle < 5 (in your
build.gradle
file):
dependencies {
compile 'io.vertx:vertx-codegen:3.9.16:processor'
compile 'io.vertx:vertx-web-api-service:3.9.16'
}
-
Gradle >= 5 (in your
build.gradle
file):
dependencies {
annotationProcessor 'io.vertx:vertx-codegen:3.9.16:processor'
annotationProcessor 'io.vertx:vertx-web-api-service:3.9.16'
compile 'io.vertx:vertx-web-api-service:3.9.16'
}
You need to import vertx-codegen
to trigger the code generation from annotated interfaces
If you want to use the interface you write in different languages, you will need to add the language dependency such as
vertx-lang-groovy
for Groovy.
Recap on Vert.x Web API Contract
Vert.x Web API Contract helps you to build a Vert.x Web router compliant to an OpenAPI spec, for example:
routerFactory.addHandlerByOperationId("operationId", routingContext -> {
RequestParameters parameters = routingContext.get("parsedParameters");
// Process the request
});
With Vert.x Web API Service you can define an event bus service that connects automatically to the router factory, allowing automatic request routing to Vert.x Event Bus.
We encourage you to read Service Proxy documentation before going further
Define your Service interface
Let’s start with defining your service interface. We want to define the service interface for this OpenAPI operations definition:
/api/transactions:
get:
operationId: getTransactionsList
parameters:
- name: from
in: query
style: form
explode: false
required: false
schema:
type: string
- name: to
in: query
style: form
explode: false
required: false
schema:
type: string
responses: ...
put:
operationId: putTransaction
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
from:
type: string
format: email
to:
type: string
format: email
value:
type: number
format: double
additionalProperties: false
required:
- from
- to
- value
responses: ...
Now we can build the interface that specifies a TransactionService that handles getTransactionsList and putTransaction operations. For each operation you need to write a method with name corresponding to operation id with following parameters:
-
Last parameter must be a
Handler<AsyncResult<OperationResponse>>
-
Second to last parameter must be a
OperationRequest
-
All parameters from first to second to last (excluded) are extracted from
RequestParameter
with specified type automatically, but they need to respect service proxy restrictions
For example:
@WebApiServiceGen
interface TransactionService {
void getTransactionsList(String from, String to, OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler);
void putTransaction(JsonObject body, OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler);
}
When you receive a request at TransactionService.getTransactionsList()
the generated service handler will automatically extract from
and to
parameter (if present) from OperationRequest
. In putTransaction()
we use the body
parameter name to extract the json body.
The service handler is also capable to translate JsonObject
to Vert.x data objects automatically, for example if you have a Transaction
data object that matches the json schema above, you can rewrite the putTransaction()
signature as:
void putTransaction(Transaction body, OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler);
Note that generated service handler matches method parameters with spec’s parameter name and not with the location of parameter, so avoid duplicated parameter names in your spec
We also encourage to extract with RequestParameter
parameters that uses json schema allOf/anyOf/oneOf/not keywords because the extraction can produce undefined behaviours
Implement your service
Now you can implement your service. The OperationRequest
object contains headers and parameters maps.
To write the request you must call the resultHandler
with an OperationResponse
. To construct the OperationResponse
you can use some handy methods like OperationResponse.completedWithJson
or OperationResponse.completedWithPlainText
For example:
resultHandler.handle(
Future.succeededFuture(
OperationResponse.completedWithPlainText(Buffer.buffer("Hello world!"))
)
);
The OperationRequest
data object
OperationRequest
it’s a serializable version of RoutingContext
. It doesn’t contain all data encapsulated in RoutingContext
. It transports to your service:
-
getHeaders
: Headers of the request -
getParams
: ContainsroutingContext.get("parsedParameters")
-
getUser
: ContainsroutingContext.user().principal()
, null if no user is authenticated -
getExtra
: Contains an extra configurable payload
You can configure a lambda that builds the extra payload with setExtraOperationContextPayloadMapper
Mount to Router Factory
Now that your service is ready you need to mount it to OpenAPI3RouterFactory
. When you use one of the methods below, the router factory mounts an handler that routes the request through the event bus to the service at the address specified. Pay attention to specify the correct address to event bus endpoint.
You have four methods to match the service with router operation handlers:
-
Mount manually every operation to a specified address with
mountOperationToEventBus
-
Mount operations matching a tag[https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#operation-object] to a specified address with
mountServiceFromTag
. -
Specify the class interface and the address of the service with
mountServiceInterface
. This method introspects the interface public methods and mounts all matching method names with operation ids -
Specify inside the OpenAPI specification the
x-vertx-event-bus
extension for operations you want to route to your service and scan all operations that contains it withmountServicesFromExtensions
We suggest you to use the extension x-vertx-event-bus
over all other methods. If you don’t want to modify your spec adding extensions, we recommend you to use mountServiceInterface
Using the extension x-vertx-event-bus
The x-vertx-event-bus
can be configured both in path item object or in operation object. It can be:
-
A string containing the address of service
-
An object containing:
-
The field
address
that specifies the address of the service -
The field
method
that specifies the interface method name (valid only when you specify it in operation object) -
The delivery options
timeout
andheaders
(Look atDeliveryOptions
)
-
For example if I want to route getTransactionsList to TransactionService
mounted at event bus address transaction_service.my_application
:
/api/transactions:
get:
operationId: getTransactionsList
parameters: ...
responses: ...
x-vertx-event-bus: transaction_service.my_application
Or
/api/transactions:
get:
operationId: getTransactionsList
parameters: ...
responses: ...
x-vertx-event-bus:
address: transaction_service.my_application
method: getTransactionsList
You can specify both in path item and operation the extension and router factory will take care of merging it:
/api/transactions:
x-vertx-event-bus:
address: transaction_service.my_application
timeout: 1000
get:
operationId: getTransactionsList
parameters: ...
responses: ...
post:
operationId: postTransaction
parameters: ...
responses: ...
x-vertx-event-bus:
method: postTransactionToDb
In this example:
-
getTransactionList
operation is mapped to service at addresstransaction_service.my_application
and methodgetTransactionList
-
postTransaction
operation is mapped to service at addresstransaction_service.my_application
and methodpostTransactionToDb
Then you can call mountServicesFromExtensions
that scans entire spec and mounts the handlers that route to your service the requests
Run the service
Now you can register your service to event bus:
TransactionService service = new TransactionServiceImpl();
final ServiceBinder serviceBinder = new ServiceBinder(vertx).setAddress("address");
MessageConsumer<JsonObject> serviceConsumer = serviceBinder.register(TransactionService.class, service);
For more info look at Vert.x service proxy documentation