Skip to content

What you should know about verticles in Vertx

Hello! I would like to dedicate a separate post to verticles in Vertx. They are core building blocks of reactive applications (although, Vertx does not force you to follow specific paradigms). Verticles come from Vertx Core library and are very tiny, compare to other components. But here is a deal – it is crucial to understand how they work and how they are connected with other Vertx units. This article explores following questions: what is a verticle in Eclipse Vertx framework, which steps does have verticle’s lifecycle, how to configure verticles and finally – how to organize your code in verticles.

What is verticle?

Verticles in Vertx are independent units of computation. They are similar to certain extent to Akka’s Actors. Both of them are used to abstract domain-specific business logic as units (entities) of reactive execution. Another example to define verticles are services. We can write an e-commerce application, that will consists of verticles for each subdomain: inventory, payments, orders, user management etc. Finally, verticles can play a role of application levels. We can use one verticle to abstract HTTP server code, another one for database operations, third – to perform API calls to extenal providers…

There are two ways to build a verticle in Vertx Core:

  1. Extend AbstractVerticle class
  2. Implement Verticle interface

Both approaches are valid, although implementing the interface may seem unnecessary. AbstractVerticle class provides to developers some useful already implemented methods, that make development easier. Also, the interface has deprecated methods, that you anyway have to implement. Finally, each verticle has references to Vertx and Context instances. If you would implement the Verticle interface, you have to manage them manually.

You can create a new verticle by using this code:

class SimpleVerticle extends AbstractVerticle{
	//...
}

In order to deploy this verticle, you need to obtain an instance of Vertx object and use deployVerticle method of it. Usually it is done inside main method of your application, like this:

class SimpleVerticle extends AbstractVerticle{}

class SimpleApplication{
	public static void main(String[] args){
		Vertx vertx = Vertx.vertx();
		vertx.deployVerticle(new SimpleVerticle());
	}
}

You can note, that this is entirely up to developer, how to organize code in verticles, and this is true, as Vertx does not force you to follow one particular paradigm.

Verticle’s lifecycle

You already have learned how to deploy a verticle. This is one step of verticle’s lifecycle. Generally, each verticle has two methods start() and stop() that are used to management its state during deployment and undeployment. Let observe steps that a typiacl Vertx verticle has:

  1. Vertx calls init method of verticle (this method is already in AbstractVerticle, but if you implement Verticle interface, you have to provide its implementation). This gives to verticle references to Vertx and Context instances. NB as a rule, one app has one Vertx instance
  2. Vertx calls start method to perform startup logic. Based on promise result, it can either be successfully completed (e.g. verticle is ready to deploy), either failed (verticle would not be deployed)
  3. Vertx deploys a verticle. Each verticle receives a unique deployment ID, that is a String that keeps UUID of deployed verticle.
  4. Vertx calls stop method to perform close logic. That means, it is a place to do work that is required before closing, like saving data or clearing resources.
  5. Finally, Vertx undeploy verticle.

Now, as you are equipped with this knowledge, let move to a practical example. Take a look on a code snippet below:

class SimpleVerticle extends AbstractVerticle {

    @Override
    public void start(Promise<Void> promise) throws Exception {
        Logger.info("Promise completed");
        promise.complete();
    }

    @Override
    public void stop(Promise<Void> promise) throws Exception {
        Logger.info("Verticle stopped...");
        promise.complete();
    }

    public static void main(String[] args) {
        Vertx vertx = Vertx.vertx();
        vertx.deployVerticle(new SimpleVerticle(), res1->{
            if (res1.succeeded()){
                // verticle is deployed
                String deployId = res1.result();
                Logger.info("Verticle deployed with id {}", deployId);
                vertx.undeploy(deployId, res2->{
                    if (res2.succeeded()){
                        // verticle undeployed
                        Logger.info("Verticle is undeployed");
                        vertx.close(res3->{
                            // vertx closed
                            if (res3.succeeded()){
                                Logger.info("Vertx closed");
                            } else {
                                // vertx is not closed
                                Logger.error(res3.cause(), "Vertx is not closed...");
                            }
                        });
                    } else {
                        Logger.error(res2.cause(), "Verticle is still running...");
                        // verticle still is running
                    }
                });
            } else {
                // deployment failed
                vertx.close(res2->{
                    // vertx closed
                    if (res2.succeeded()){
                        Logger.info("Vertx closed");
                    } else {
                        // vertx is not closed
                        Logger.error(res2.cause(), "Vertx is not closed...");
                    }
                });
            }
        });
    }
}

This code represents lifecycle steps, that we already discussed. It performs following logic:

  1. Deploy a new SimpleVerticle instance
  2. Get a deployment ID
  3. Undeploy a verticle by its ID
  4. Close Vertx instance

You can note, that most methods provide async result callbacks. Basically they have several imporant methods, you need to know:

  • succeeded – returns a boolean value that represents success of executing an operation (true/false)
  • result – object that is a result of operation (concrete data type depends on exact handler)
  • cause – returns Throwable describing failure. This will be null if the operation succeeded.

Configuring verticles

Each verticle can receive a configuration on startup. There are two ways to provide configuration to verticle:

  1. Pass config during deployment
  2. Using Vertx-Config library

First approach is performed with Vert.x Core library tools. Second is much more feature rich. It offers abstraction layer for various type of configs – properties files, system vars; as well some more exotic stores, that are available via extensions – Vault, Kubernetes and others.

We will desciribe both methods in this section.

Option A. Passing configuration during deployment

This method is simpler, however it is much more limited. You can pass JsonObject that contains a configuration, during deployment. Vertx has several overloaded versions of deployVerticle method. Some of them accept DeploymentOptions as an argument. This object keeps options for configuring a verticle deployment. You can use setConfig method in order to add configuration object. Let explore required actions:

  1. Create JsonObject with configuration
  2. Create DeploymentOptions
  3. Pass JSON config to DeploymentOptions with setConfig method
  4. Provide DeploymentOptions during verticle’s deployment
  5. Retrive JsonObject with config inside verticle using config method

The code snippet below demonstates these phases:

class SimpleVerticle extends AbstractVerticle {

    @Override
    public void start(Promise<Void> promise) throws Exception {
    	JsonObject config = config();
    	System.out.println(config.encode());
        promise.complete();
    }

    public static void main(String[] args) {
        Vertx vertx = Vertx.vertx();
        DeploymentOptions options = new DeploymentOptions();
        JsonObject config = new JsonObject().put("user", "admin").put("url", "https://www.example.com/api/v1/");
        options.setConfig(config);
        vertx.deployVerticle(new SimpleVerticle(), options);
    }
}

Option B. Using Vertx config library

Vertx configuration model is a programming abstraction, which is implemented by Vertx-config library. There are two main components, that we use in order to read configuration in Vert.x apps: ConfigProvider and ConfigStore. Both are interfaces that define abstractions. ConfigProvider reads configuration and tracks for updates. ConfigStore abstracts type and format of configuration, for instance it can be JSON, key-value or Yaml format (requires extension) and can be placed in file system, system variables or in remote source. We also use options: ConfigStoreOptions and ConfigRetrieverOptions that hold data structures, we pass into the retriever or the config store.

Basically, the flow of getting configuration is straightforward:

  1. Define ConfigStore
  2. Pass ConfigStore to ConfigRetrieverOptions
  3. Create ConfigRetriever with specified ConfigRetrieverOptions

Let have a practical example and get configuration from Java Properties file. I advise you to follow my dedicated post on vertx config library and different ConfigStore options for more information. Basically, following steps are identical for all ConfigStores.

Step 1. Create ConfigStoreOptions

ConfigStoreOptions options = new ConfigStoreOptions();
options.setType("file");
options.setFormat("properties");
options.setConfig(new JsonObject().put("path", "conf.properties"));

Step 2. Create retriever options

ConfigRetrieverOptions retriverOptions = new ConfigRetrieverOptions()
				.addStore(options);

Step 3. Create config retriever

We can attach config retriever to verticle start method.

public void start(Promise<Void> promise) throws Exception {
	ConfigRetriver retriever = ConfigRetriever.create(vertx, retriverOptions);
	retriever.getConfig(result->{
			if (res.succeeded()) {
				JsonObject config = res.result();
				System.out.println(config);
				promise.complete();
			} else {
				promise.fail("Cannot get configuration");
			}
	});

NB Despite of type and format of config store you use, ConfigRetriever obtains configuration as JsonObject. This is very useful as it provides an abstraction and removes dependency on conrete configuration format. Also, in real-life applications (while I hate this sentence – usually when it is said, it refers to “coding in Java 1.3 in 2019”) you should protect your credentials, passed to services. It is advisable to use something like Hashicorp Vault. Please take a look on my article on how to use Vertx with Hashicorp Vault.

How to organize your code in verticles

For bigger projects we usually need separate client-side logic from backend and inter connect them using some RESTful API. In this case, Vertx-based app does not handle view rendering, but only backend logic. We need to “define” role of verticles and decide which of them would perform blocking or non-blocking operations and therefore place to worker threads.

We can define components on two basis:

  1. Layer
  2. Domain (business subdomain)

Separation by layer

When we separate components on layer basis we encapsulate to each component logic, that is specific for each application layer. That can be layers for database access, 3rd party APIs consuming, HTTP operations etc. Take a look on the following graph:

Graph 1. Organize verticles by layer

n the example above we have several layers, each of which are placed in a separate verticle:

  • API verticle is responsible for handling HTTP REST API calls and authentication
  • Database verticle handles data storage access
  • Provider verticle is a component that connects your app with other cloud API, like CRMs, payment providers or weather API

Both database and provider verticles should also be workers as they consits of code that easily blocks main thread. Verticles are connected with eventbus messaging between each other.

Separation by subdomain

When you API grows bigger, it may be more wise to separate components not by layer, but by domain. This is known as a domain-driven design, which usually means that parts are defined by some subdomain. For example, we can have an e-commerce platform that have components like inventory service, order service, user service etc. What is crucial that each component has bounded context. With Vertx you can use an approach that I call Endpoint-Service-Repository. Take a look on the graph:

Graph 2. Organize verticles by subdomain

What you can note from the picture? Perhaps, following points:

  • Each component has its own data access object (repository) that is usually placed inside component’s verticle, not in a separate one (you can perform it by using non-blocking code techniques)
  • Components are connected to one endpoint, and are not connected with each other.

In this architecture communication with an outer world is performed via API gateway or endpoint, as you like to call it. It exposes REST API that external clients consumes. Verticles usually don’t connect with each other, as messaging with the endpoint is operated via same eventbus patterns, like in previous cases. This is a very good idea to organize your code in this manner, as this is almost microservices architecture. If you need to do something bigger, take a look on the next candidate.

Basically these principles lay in the ground of Vertx architectural patterns. There are four most common architectural approaches you could use.

Conclusion

Ok, I hope this post will help you to better understand a nature of Vertx verticles. As they are so important in Vertx ecosystem, I think they really desire a separate article. Here, we defined what is a verticle and which steps correspond to verticle’s lifecycle. We also looked deeper on configuring verticles and, finally, explored how to organize your code in verticles.

References

  • Clement Escoffier Building Reactive microservices in Java 1st edn. O’Reilly, 2017
  • Yuri Mednikov A comprehensive guide to Vert.x Core Eventbus (2019) Mednikov, read here
  • Yuri Mednikov Four strategies to design Vertx applications (2019) Mednikov, read here
Copy link
Powered by Social Snap