Migrating from Vert.x to Quarkus

David
Geek Culture

--

If you are already on a Vert.x based application, or any type that make use of reactive programming libraries within Java, perhaps the missing piece of the puzzle is having that cloud native and MicroProfile compliant stack like Quarkus and its eco system added to your application. Whether its Jakarta EE, Spring, Vert.x or some other in house produced stack, there are a few ways.

This guide will mostly swift through how one would go by coming from a native Vert.x stack and what can be expected when moving over.

The Why?

What do Vert.x and Quarkus have in common?

The Eclipse Vert.x is a toolkit for building reactive applications. It is designed to be lightweight and embeddable. Vert.x defines a reactive execution model and provides a large ecosystem. Quarkus is based on Vert.x, and almost all network-related features rely on Vert.x. While a lot of reactive features from Quarkus don’t show Vert.x, it’s used under the hood. On top of that is is also Quarkus capability to be able to compile JVM bytecode to a native executable.

This quote from the “Vert.x in Action” frames the common approaches for what to use the frameworks for.

Quarkus unifies imperative and reactive programming models, and Vert.x is a cornerstone of the framework. Vert.x is not just used to power some pieces of the networking stack; some client modules are directly based on those from Vert.x, such as the Quarkus mail service and reactive routes. You can also use Vert.x APIs in a Quarkus application, with the unification between reactive and imperative helping you to bridge both worlds. Vert.x and Quarkus have different programming paradigms: Vert.x will appeal to developers who prefer a toolkit approach, or developers who have affinities with Node.js. In contrast, Quarkus will appeal to developers who prefer an opinionated stack approach with dependency injection and convention over configuration. In the end, both projects work together, and anything you develop with Vert.x can be reused in Quarkus.

Vert.x in action by Julien Ponge

Other reasons for migrating could also be wanting to:

  • Investigate native compilation and memory usage over time
  • Investigate the extra capabilities within MicroProfile
  • Investigate overall performance in application but also development time

Prerequisites

  • Brief knowledge about Vert.x in general

Preface

Before starting, these findings mainly apply to version ≥ 4 of Vert.x . There are a quite a few number of client modules in Vert.x omitted in this article for the sake of the scope.

Migrating

Assume say we have a stack based on Vert.x, how seamless would the process be of transitioning over? Is it as seamless as marketed by the community? Let’s find out!

There are some key points to consider though, is you current application running any blocking code? Even though that might be the case, Quarkus offers RESTEasy Reactive routes for running blocking code with the @Blocking annotation. More on this subject later.

With the Quarkus framework heavily dependent on the Vert.x thread pool since its inception, maybe the benefits of moving over outweighs the cons when you get a cloud native stack as a bargain, and even if you don’t have a reactive application in mind, there still might be other benefits to reap.

SmallRye Mutiny Vert.x Bindings

With Quarkus, you also get Mutiny which is an intuitive event-driven reactive programming library for Java. The Vert.x bindings simplify reactive programming for both pure Vert.x and Quarkus applications. The mutiny Vert.x are in loose terms wrappers for all of the Vert.x APIs.

So if you are familiar with Vert.x, you know that Vert.x provides different API models. The bare Vert.x API uses callbacks, while the Mutiny variant uses Uni and Multi.

The bindings are wrapped by this annotation underneath.

@MutinyGen(<Vert.x client class>)
Mutiny generator produces Vert.x bindings (from quarkus.io)

Accessing Vert.x

You may inject any of the two flavors of Vertx as well as the EventBus in your Quarkus application beans.

You will pick one or the other depending on your use cases.

  • bare: for advanced usage or if you have existing Vert.x code you want to reuse in your Quarkus application
  • mutiny: Mutiny uses two types of response: Uni and Multi. This is currently the recommended API.

Once the quarkus-vertx extension has been added, you can access the managed Vert.x instance using @Inject depending on what flavor of choice you had.

  • @Inject io.vertx.mutiny.core.Vertx vertxMutiny api
  • @Inject io.vertx.core.Vertx vertx bare api

I will present just a few examples of two different client extensions. However, below you can find an extensive list of what type of Vert.x bindings the smallrye mutiny is currently supporting.

AbstractVerticle

Assuming that your current Vert.x project extending this base abstract class.

import io.vertx.core.AbstractVerticle;

Migrating this to Quarkus, you would start adding adding the quarkus-vertx extension.

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-web</artifactId>
</dependency>

In Quarkus this naturally resolves to this import and abstract class which holds the Vert.x instance context and is resolvable via CDI.

import io.smallrye.mutiny.vertx.core.AbstractVerticle;

Using Vert.x web clients

As you can inject a Vert.x instance seen above, you can use Vert.x clients in a Quarkus application in similar fashion. This section gives an example with the WebClient. Adding the extension, exposes the bare API and its corresponding Vert.x mutiny wrapper by using the following extension.

<dependency>
<groupId>io.smallrye.reactive</groupId>
<artifactId>smallrye-mutiny-vertx-web-client</artifactId>
</dependency>

Usage

bare api

import io.vertx.ext.web.client.WebClient;

or Mutiny API

import io.vertx.mutiny.ext.web.client.WebClient;

Using Vert.x postgres db client

Same applies for instance for the postgres db client

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-pg-client</artifactId>
</dependency>

Once imported you can start using either of these two APIs.

bare API

import io.vertx.sqlclient.SqlClient;

Mutiny API

import io.vertx.mutiny.sqlclient.SqlClient;

In other words. There is always the possibility to fallback on the bare api if deemed necessary for more advanced usage.

Our current MainVerticle

Let’s suppose we have the following Verticle in a Vert.x native application below which exposes different CRUD routes, how would we handle the different approaches?

Bare API migration approach

As previously seen, from a Vert.x bare API perspective, nothing really changes. All bare APIs are exposed when using the necessary mutiny client extensions or just the quarkus-vertx extension. Now beans can be managed by a CDI context which leverages the Vert.x instance as a CDI bean which we can use to register our custom routes. It’s done by this method which observes startup events of the application.

public void init(@Observes StartupEvent e, Vertx vertx, MainVerticle verticle, Router router) {

router.route().handler(BodyHandler.create());
registerRoutes(router);
vertx.deployVerticle(verticle).await().indefinitely();
}

However, if want really want to debunk all of the magic, there is a good article that describes setting up the Vert.x instance running in Quarkus programmatically here.

Mutiny API migration approach

Taking it a step further, in this approach if one would one to start using the wrapping Mutiny API for the postgres db client, one would have to resolve their new imports from the quarkus-reactive-pg-client extension.

bare API

import io.vertx.mutiny.pgclient.PgPool;
import io.vertx.mutiny.sqlclient.SqlClient;

Mutiny api

import io.vertx.mutiny.sqlclient.SqlClient;
import io.vertx.mutiny.pgclient.PgPool;

Usage as follows

// Create the client pool
SqlClient client = PgPool.client(connectOptions, poolOptions);
client.query("<DB QUERY>").execute().toMulti();

The change is minimal and now we can start using our Uni and Multi response types. That might come at a certain cost however, with different learning curves for the Mutiny reactive API.

Reactive Route Approach

Reactive routes propose an alternative approach to implement HTTP endpoints where you declare and chain routes. Quarkus also offers the possibility to use reactive routes. You can implement REST API with routes only or combine them with JAX-RS resources and servlets like we will demonstrate as last step.

REST Easy Reactive Routes with Mutiny API approach

As a last step, building on the previous original Verticle example with the Vert.x router, we can transform our router by layering it with JAX-RS annotated methods and go the full distance by using RESTEasy Reactive and skip the manual registration of routes. Most of the heavy lifting is perhaps using the Mutiny API, so this usually just adding an additional layer to your service for becoming a bit more coherent.

What is REST Easy Reactive?

A new JAX-RS implementation written from the ground up to work on our common Vert.x layer and is thus fully reactive, while also being very tightly integrated with Quarkus and consequently moving a lot of framework specific work (like annotation scanning and metamodel generation) to build time.

REST Easy Reactive supports CDI, Security, Metrics, JSON, Qute, Bean Validation, OpenAPI and other rich features targeted and requested by its community.

Let’s play it out in our example.

The case for blocking calls

There could be cases where blocking operations are inevitable, perhaps coming from a none Vert.x stack. Quarkus offers a way of dealing with those type of calls, forcing them to use the worker thread pool. This is done by annotating the method with @Blocking

Since RESTEasy Reactive is implemented using two main thread types, there are two different thread models available.

  • Event-loop threads: which are responsible, among other things, for reading bytes from the HTTP request and writing bytes back to the HTTP response
  • Worker threads: they are pooled and can be used to offload long-running operations

A lot of the modules in Quarkus are written in a reactive manner and that’s why this explicit annotation is needed if you were to migrate from an application that invokes blocking calls.

An additional step would be looking further at the different MicroProfile functions and see what they offer in terms of security, resilience, monitoring or tracing for the application.

--

--