Combine vert.x and mongo to build a giant
This blog post is part of the introduction to vert.x series. Last time, we have seen how we can use the vertx-jdbc-client
to connect to a database using a JDBC driver. In this post, we are going to replace this JDBC client by the vertx-mongo-client
, and thus connect to a Mongo database.
You don’t understand the title, check the mongoDB website.
But before going further, let’s recap.
Previously in ‘introduction to vert.x’
- The first post has described how to build a vert.x application with Maven and execute unit tests.
- The second post has described how this application can become configurable.
- The third post has introduced vertx-web, and a small collection management application has been developed. This application offers a REST API used by a HTML/JavaScript frontend.
- The fourth post has presented how you can run integration tests to ensure the behavior of your application.
- The last post has presented how you can interact with a JDBC database using the vertx-jdbc-client.
This post shows another client that lets you use MongoDB in a vert.x application. This client provides an vert.x API to access asynchronously to the Mongo database. We won’t compare whether or not JDBC is superior to Mongo, they have both pros and cons, and you should use the one that meet your requirements. Vert.x lets you choose, that’s the point.
The vertx-mongo-client documentation is available here.
The code developed in this blog post is available in the branch post-6. Our starting point is the code from the post-5 branch.
Asynchronous data access
One of the vert.x characteristics is being asynchronous. With an asynchronous API, you don’t wait for a result, but you are notified when this result is ready. Thanks to vert.x, this notification happens in the same thread (understand event loop) as the initial request:
Your code (on the left) is going to invoke the mongo client and pass a callback that will be invoked when the result is available. The invocation to the mongo client is non blocking and returns immediately. The client is dealing with the mongo database and when the result has been computed / retrieved, it invokes the callback in the same event loop as the request.
This model is particularly powerful as it avoids the synchronization pitfalls. Indeed, your code is only called by a single thread, no need to synchronize anything.
As with every Maven project…
… we need to update the pom.xml
file first.
In the pom.xml
file, replace the vertx-jdbc-client
by the vertx-mongo-client
:
Unlike JDBC where we were instantiating a database on the fly, here we need to explicitly starts a MongoDB server. In order to launch a Mongo server in our test, we are going to add another dependency:
This dependency will be used in our unit tests, as it lets us start a mongo server programmatically. For our integration tests, we are going to use a Maven plugin starting and stopping the mongo server before and after our integration tests. Add this plugin to the <plugins/>
section of your pom.xml
file.
Notice the port we use here (37017), we will use this port later.
Enough XML for today
Now that we have updated our pom.xml
file, it’s time to change our verticle. The first thing to do is to replace the jdbc client by the mongo client:
This client is configured with the configuration given to the verticle (more on this below).
Once done, we need to change how we start the application. With the mongo client, no need to acquire a connection, it handles this internally. So our startup sequence is a bit more simple:
As in the previous post, we need to insert some predefined data if the database is empty:
To detect whether or not the database already contains some data, we retrieve the number of documents from the whiskies
collection. This is done with : mongo.count(COLLECTION, new JsonObject(), count -> {})
. The second parameter is the query. In our case, we want to count all documents. This is done using new JsonObject()
that would create a query accepting all documents from the collection (it’s equivalent to a SELECT * FROM ...
).
Also notice the insert
calls. Documents are passed as JSON object, so to insert an object, just serialize it to JSON and use mongo.insert(COLLECTION, json, completion handler)
.
Mongo-ize the REST handlers
Now that the application boot sequence has been migrated to mongo, it’s time to update the code handling the REST requests.
Let’s start by the getAll
method that returns all stored products. To implement this, we use the find
method. As we saw for the count
method, we pass an empty json object to describe a query accepting all documents:
The query results are passed as a list of JSON objects. From this list we can create our product instances, and fill the HTTP response with this set.
To delete a specific document we need to select the document using its id
:
The new JsonObject().put("_id", id)
describes a query selecting a single document (selected by its unique id
, so it’s the equivalent to SELECT * WHERE id=...
). Notice the _id
which is a mongo trick to select a document by id.
Updating a document is a less trivial:
As we can see, the update
method takes two JSON objects as parameter:
- The first one denotes the query (here we select a single document using its id).
- The second object expresses the change to apply to the selected document. It uses a mongo syntax. In our case, we update the document using the
$set
operator.
In this code we update the document and replace only a set of fields. You can also replace the whole document using mongo.replace(...)
.
I definitely recommend to have a look to the MongoDB documentation, especially:
Time for configuration
Well, the code is migrated, but we still need to update the configuration. With JDBC we passed the JDBC url and the driver class in the configuration. With mongo, we need to configure the connection_string
- the mongo://
url on which the application is connected, and db_name
- a name for the data source.
Let’s start by the unit test. Edit the MyFirstVerticleTest
file and add the following code:
Before our tests, we start (programmatically) a mongo database on the port 12345. When all our tests have been executed, we shutdown the database.
So now that the mongo server is managed, we need to to give the right configuration to our verticle. Update the DeploymentOption
instance with:
That’s all for the unit tests.
For the integration-test, we are using an externalized json file. Edit the src/test/resources/my-it-config.json
with the following content:
Notice the port we are using for the mongo server. This port was configured in the pom.xml
file.
Last but not least, we still have a configuration file to edit: the configuration you use to launch the application in production
:
Here you would need to edit the localhost:27017
with the right url for your mongo server.
Because mongo document id are String and not integer, we have to slightly change document selection in the integration test.
Time for a run
It’s time to package and run the application and check that everything works as expected. Let’s package the application using:
And then to launch it, start your mongo server and launch:
If you are, like me, using docker / docker-machine for almost everything, edit the configuration file to refer to the right host (localhost for docker, the docker-machine ip if you use docker-machine) and then launch:
That’s all folks!
We are reaching the end of this post. We saw how you can use the vert-mongo-client to access asynchronously data stored inside a mongo database as well as inserting/updating this data. Now you have the choice between JDBC or Mongo. In addition, vert.x provides a client for Redis.
Next time, we will see how the verticle class can be split in two verticles in order to better organize your code. The interaction between the two verticles will uses services.
Stay tuned & Happy coding!