Vert.x — Learnings About a Reactive Framework


A framework that provides a lot of tools for building reactive applications on the JVM

By Lars Leithold, Senior Software Engineer at Project A

We at Project A are always looking for technologies that meet the modern demands of building responsive, scalable, flexible and resilient systems. With Vert.x we found a solution that is becoming more and more popular: A framework that will provide you with a lot of tools for building reactive applications on the JVM.

What is Vert.x?

A typical Vert.x application consists of multiple verticles, which are modules that can be deployed and scaled independently. Verticles communicate with each other by sending messages over an event bus. Since Vert.x is a polyglot framework, you can implement each verticle in a different programming language (currently officially supported languages: Java, JavaScript, Groovy, Ruby, Ceylon, Scala and Kotlin).

Vert.x is unopinionated, which means that it doesn’t restrict you in the way how you want to write your application. The ideal use case for Vert.x is probably an event-driven microservice architecture, but you could also use it to build a monolithic application or just include certain Vert.x tools into a Spring application for example. It’s also very modular, so you can define exactly the tools that you need, and use only these and nothing more, which leads to very lightweight and fast applications that can also run on small hardware.

There is only one important thing that you should always keep in mind. Vert.x is event-driven and non-blocking. So you usually define event handlers, that are called by Vert.x when a certain event occurs. To pass those events to the handlers, Vert.x uses a thread which is called “event-loop”. So, be careful! You should never execute any blocking code in your handler because this would also block the event-loop and slow down the whole application. A typical piece of blocking code would be for example a call to a database and waiting for the result. Fortunately Vert.x provides certain ways to handle such cases, like non-blocking clients for some popular databases, or wrapping the blocking code in a special handler.

Pros and cons

The advantage of writing non-blocking code is a performance gain. The application needs to use much fewer threads than a conventional application where usually each request is handled in a separate thread. And fewer threads means less overhead, better CPU usage and better scaling on many parallel requests. The disadvantage, however, is that non-blocking code is usually a bit more difficult to read, to write and to debug. Although there are of course exceptions. For example, the verticles are threadsafe per default, because they are only accessed by one thread, which can reduce the code complexity in some cases. I also believe that the strict usage of callbacks in an event-driven system of modular verticles automatically encourages decoupling and writing highly cohesive code, which is very good. But you always have to be careful that you don’t accidentally include any blocking code, or create a “callback hell” when using many nested callbacks. You should also keep in mind, that you shouldn’t use any libraries that use blocking code, which is the case for most of all third-party libraries. Although as I already wrote, there are ways to execute blocking code if it really can’t be avoided.

Also worth to mention, if you have to hire developers, it’s probably not so easy to find some which already have experience with Vert.x, since it’s not (yet) widely spread. But on the other hand, I can recommend every developer to have a look into Vert.x and learn it, especially when you are not yet familiar with the reactive coding style.

Documentation and community

The official Vert.x documentation is very extensive, nicely grouped by topic and with many manuals and examples in multiple programming languages. These examples are usually kept quite simple, which makes sense I guess, in order to not overwhelm the reader and to focus on one certain tool or use case. However, if you are new to the whole reactive programming style, you might miss some best practices on how to keep your code clean and nicely structured in a bigger and more complex project. On the other side, that leaves some room your own creativity 😉

The community of Vert.x seems to be still relatively small. For example, there are only 1,523 questions on StackOverflow tagged with Vert.x (compared to 153,358 questions for Spring). The main community channel for Vert.x seems to be a google group with currently 8,921 topics. Although small, the community seems to be very enthusiastic, providing a lot of tutorials, examples, blog posts and additional tools and libraries.

Performance Test Vert.x vs. Spring Boot

Out of curiosity, I created a small test to compare the performance of a simple Vert.x application with a common Spring Boot application. It’s a very basic website, which is just rendering a “Hello World!” string into a Thymeleaf template. There are 2 endpoints, the first is directly returning the result, and the second is simulating a typical case when you have to retrieve the result from a slow database or another service (I’m doing that by calling a second application, which is waiting 100 milliseconds to return a result).

Vert.x example

public class WebVerticle extends AbstractVerticle {
private ThymeleafTemplateEngine engine;
private WebClient client;
@Override
public void start() {
engine = ThymeleafTemplateEngine.create (vertx);
client = WebClient.create(vertx, new WebClientOptions() .setMaxPoolSize(100));
Router router = Router.router (vertx);
router. route("/test1").handler (this: :handleTest1) ;
router.route ("/test2").handler (this ::handleTest2);
vertx.createHttpServer().requestHandler(router).listen (8080) ;
}
private void handleTest1 (RoutingContext ctx) {
JsonObject model = new JsonObject().put ("welcome", "Hello World!");
engine. render (model, "templates/index.html", handleRenderResult(ctx)) ;
}
private void handleTest2 (RoutingContext ctx) {
client.get (8081, "localhost", " /"). send(ar -> {
if (ar .succeeded()) {
String response = ar. result().bodyAsString();
JsonObiect model = new JsonObiect().put ("welcome", response);
engine. render (model, "templates/index.html", handleRenderResult(ctx)) ;
} else {
System.out .printIn("Something went wrong: " + ar. cause().getMessage ()) ;
}
});
}
private Handler<AsyncResult<Buffer>> handleRenderResult(RoutingContext ctx) {
return ar -> {
if (ar.succeeded( )) {
ctx. response().end(ar.result()) ;
} else
ctx.fail(ar.cause()) ;
}
};
}
}Code language: PHP (php)

Spring Boot example

@Controller
public class WebController {
@RequestMapping(value = "/test", method = RequestMethod.GET)
public String test1 (Model model) {
model.addAttribute ("welcome", "Hello Word!");
return "index";
}
@RequestMapping (value = "/test2" method = RequestMethod.GET)
public String test2 (Model model) throws IOException {
String response = Request. Get("http://localhost :8081/").execute ( )
. returnContent ( ) .asString() ;
model.addAttribute("welcome", response);
return "index";
}
}Code language: JavaScript (javascript)
Vert.x Test1Spring Boot Test1
Concurrency Level: 10
Time taken for tests: 58.640 seconds
Complete requests: 1000000
Requests per second: 17053.09 [#/sec]
Time per request: 0.586 [ms]
Concurrency Level: 10
Time taken for tests: 205.175 seconds
Complete requests: 1000000
Requests per second: 4873.89 [#/ sec]
Time per request: 2.052 [ms]
Performance Test Vert.x vs. Spring Boot

As you can see in the first test, Vert.x is significantly faster compared to Spring Boot. Let’s have a look at the CPU and memory usage during the test:

Vert.x:

Vert.x CPU and memory usage

Spring Boot:

Spring Boot CPU and memory usage

As expected from a lightweight framework, Vert.x consumes much less CPU and memory over time. But this is just a very simple test case. More interesting is the second test:

Vert.x Test2Spring Boot Test2
Concurrency Level: 10
Time taken for tests: 103.471 seconds
Complete requests: 10000
Requests per second: 96.65 [#/sec]
Time per request: 103.471 [ms]

Concurrency Level: 100
Time taken for tests: 10.488 seconds
Complete requests: 10000
Requests per second: 953.46 [#/sec]
Time per request: 104.881 [ms]
Concurrency Level: 10
Time taken for tests: 106.633 seconds
Complete requests: 10000
Requests per second: 93.78 [#/sec]
Time per request: 106.633 [ms]

Concurrency Level: 100
Time taken for tests: 11.779 seconds
Complete requests: 10000
Requests per second: 848.99 [#/sec]
Time per request: 117.787 [ms]
Performance Test Vert.x vs. Spring Boot

Because of the 100 ms waiting time in it, the minimum time per request is 100 ms in this test, so only the additional time is relevant here. With 10 concurrent requests, you can already see a slight advantage for Vert.x, but with 100 concurrent requests, the difference is much more obvious. Again as expected, Vert.x is significantly faster. Due to the use of fewer threads, Vert.x non-blocking approach performs a lot faster than Spring Boot that needs to create at least 100 parallel threads in this example.

A small side note to this second test: at the first try I got really bad results for Vert.x, and it got much worse when I increased the concurrency level, which was totally not what I expected. It turned out that the bottleneck was the non-blocking WebClient that I’m using in the Vert.x example. It has a default max size for its connection pool of 5 connections. Setting it to 100 solved the issue. In hindsight, it’s somehow obvious, but it took me quite some time to figure that out since it was also not really mentioned in the documentation.

Conclusion

There is no framework that is the best for each and every use case. Vert.x is a good choice for an event-driven microservice architecture that can handle many parallel requests in a resource-efficient way.

But that comes with the price of a higher implementation effort, compared to for example Spring Boot, which is often more convenient to use (as you can also see in my little code examples above). Spring Boot also offers some tools for reactive programming, but they are just added to the existing Spring framework (since version 5). Whereas Vert.x is completely rooted in the reactive concept, which makes it feel much more natural and consequent for me to implement reactive code in Vert.x.