Tip
|
The corresponding source code is in the step-2 folder of the guide repository.
|
The first iteration got us a working wiki application. Still, its implementation suffers from the following issues:
-
HTTP requests processing and database access code are interleaved within the same methods, and
-
lots of configuration data (e.g., port numbers, JDBC driver, etc) are hard-coded strings in the code.
This second iteration is about refactoring the code into independent and reusable verticles:
We will deploy 2 verticles to deal with HTTP requests, and 1 verticle for encapsulating persistence through the database. The resulting verticles will not have direct references to each other as they will only agree on destination names in the event bus as well as message formats. This provides a simple yet effective decoupling.
The messages sent on the event bus will be encoded in JSON. While Vert.x supports flexible serialization schemes on the event bus for demanding or highly-specific contexts, it is generally a wise choice to go with JSON data. Another advantage of using JSON is that it is a language-agnostic text format. As Vert.x is polyglot, JSON is ideal should verticles written in different languages need to communicate via message passing.
The verticle class preamble and start
method look as follows:
link:src/main/java/io/vertx/guides/wiki/HttpServerVerticle.java[role=include]
-
We expose public constants for the verticle configuration parameters: the HTTP port number and the name of the event bus destination to post messages to the database verticle.
-
The
AbstractVerticle#config()
method allows accessing the verticle configuration that has been provided. The second parameter is a default value in case no specific value was given. -
Configuration values can not just be
String
objects but also integers, boolean values, complex JSON data, etc.
The rest of the class is mostly an extract of the HTTP-only code, with what was previously database code being replaced with event bus messages.
Here is the indexHandler
method code:
link:src/main/java/io/vertx/guides/wiki/HttpServerVerticle.java[role=include]
-
The
vertx
object gives access to the event bus, and we send a message to the queue for the database verticle. -
Delivery options allow us to specify headers, payload codecs and timeouts.
-
Upon success a reply contains a payload.
As we can see, an event bus message consists of a body, options, and it can optionally expect a reply.
In the event that no response is expected there is a variant of the send
method that does not have a handler.
We encode payloads as JSON objects, and we specify which action the database verticle should do through a message header called action
.
The rest of the verticle code consists in the router handlers that also use the event-bus to fetch and store data:
link:src/main/java/io/vertx/guides/wiki/HttpServerVerticle.java[role=include]
Connecting to a database using JDBC requires of course a driver and configuration, which we had hard-coded in the first iteration.
While the verticle will turn the previously hard-coded values to configuration parameters, we will also go a step further by loading the SQL queries from a properties file.
The queries will be loaded from a file passed as a configuration parameter or from a default resource if none is being provided. The advantage of this approach is that the verticle can adapt both to different JDBC drivers and SQL dialects.
The verticle class preamble consists mainly of configuration key definitions:
link:src/main/java/io/vertx/guides/wiki/WikiDatabaseVerticle.java[role=include]
SQL queries are being stored in a properties file, with the default ones for HSQLDB being located in src/main/resources/db-queries.properties
:
link:src/main/resources/db-queries.properties[role=include]
The following code from the WikiDatabaseVerticle
class loads the SQL queries from a file, and make them available from a map:
link:src/main/java/io/vertx/guides/wiki/WikiDatabaseVerticle.java[role=include]
We use the SqlQuery
enumeration type to avoid string constants later in the code.
The code of the verticle start
method is the following:
link:src/main/java/io/vertx/guides/wiki/WikiDatabaseVerticle.java[role=include]
-
Interestingly we break an important principle in Vert.x which is to avoid blocking APIs, but since there are no asynchronous APIs for accessing resources on the classpath our options are limited. We could use the Vert.x
executeBlocking
method to offload the blocking I/O operations from the event loop to a worker thread, but since the data is very small there is no obvious benefit in doing so. -
Here is an example of using SQL queries.
-
The
consumer
method registers an event bus destination handler.
The event bus message handler is the onMessage
method:
link:src/main/java/io/vertx/guides/wiki/WikiDatabaseVerticle.java[role=include]
We defined a ErrorCodes
enumeration for errors, which we use to report back to the message sender.
To do so, the fail
method of the Message
class provides a convenient shortcut to reply with an error, and the original message sender gets a failed AsyncResult
.
So far we have seen the complete interaction to perform a SQL query:
-
retrieve a connection,
-
perform requests,
-
release the connection.
This leads to code where lots of error processing needs to happen for each asynchronous operation, as in:
link:src/main/java/io/vertx/guides/wiki/WikiDatabaseVerticle.java[role=include]
Starting from Vert.x 3.5.0, the JDBC client now supports one-shot operations where a connection is being acquired to do a SQL operation, then released internally. The same code as above now reduces to:
link:src/main/java/io/vertx/guides/wiki/WikiDatabaseVerticle.java[role=include]
This is very useful for cases where the connection is being acquired for a single operation. Performance-wise it is important to note that re-using a connection for chained SQL operations is better.
The rest of the class consists of private methods called when onMessage
dispatches incoming messages:
link:src/main/java/io/vertx/guides/wiki/WikiDatabaseVerticle.java[role=include]
We still have a MainVerticle
class, but instead of containing all the business logic like in the initial iteration, its sole purpose is to bootstrap the application and deploy other verticles.
The code consists in deploying 1 instance of WikiDatabaseVerticle
and 2 instances of HttpServerVerticle
:
link:src/main/java/io/vertx/guides/wiki/MainVerticle.java[role=include]
-
Deploying a verticle is an asynchronous operation, so we need a
Future
for that. TheString
parametric type is because a verticle gets an identifier when successfully deployed. -
One option is to create a verticle instance with
new
, and pass the object reference to thedeploy
method. Thecompleter
return value is a handler that simply completes its future. -
Sequential composition with
compose
allows to run one asynchronous operation after the other. When the initial future completes successfully, the composition function is invoked. -
A class name as a string is also an option to specify a verticle to deploy. For other JVM languages string-based conventions allow a module / script to be specified.
-
The
DeploymentOption
class allows to specify a number of parameters and especially the number of instances to deploy. -
The composition function returns the next future. Its completion will trigger the completion of the composite operation.
-
We define a handler that eventually completes the
MainVerticle
start future.
The astute reader will probably wonder how we can deploy the code for a HTTP server on the same TCP port twice and not expect any error for either of the instances, since the TCP port will already be in use. With many web frameworks we would need to choose different TCP ports, and have a frontal HTTP proxy to perform load balancing between the ports.
There is no need to do that with Vert.x as multiple verticles can share the same TCP ports. Incoming connections are simply distributed in a round-robin fashion from accepting threads.