rhoar components

RHOAR: Vert.x Microservices Toolkit Compared to Fuse Integration Services – Part 1

1 Introduction

Red Hat recently released the Red Hat Openshift Application Runtimes (RHOAR) which includes a number of useful frameworks/toolkits including: Wildfly Swarm, Vert.x, Spring Boot, Netflix OSS and node.js (technology preview).

Among the RHOAR components, Vert.x is completely new to me. Vert.x is a polyglot toolkit for building reactive applications on the JVM. This means that, like node.js, it is event driven and non-blocking and can handle high concurrency using a small number of kernel threads. In other words, it can scale with minimal hardware. In this article, I am going to experiment with Vert.x and compare it to Fuse Integration Servcies (FIS) in building a cloud native microservice. So, please join me on my journey into the world of Vert.x and build a Vert.x application which uses dependency injection deployed on Openshift.

2 A FIS Application

One of the labs in Red Hat’s Agile Integration Workshop is to use FIS with Spring Boot to build a RESTful service to illustrate how easy it is to use FIS’ Apache Camel REST DSL and Spring Boot to build cloud native microservices involving use of a relational database. I am using a slightly enhanced version here for comparing with a Vert.x implementation. The complete source code can be found here.

2.1 Spring Boot and REST DSL

The Camel routes are shown diagrammatically below:

Route Diagram

You can see the source in Github.

By using Spring Boot’s auto-configuration, the database is set up automatically when running locally on Developer Studio using the H2 in-memory database. We only have to include the dependencies in pom.xml and create application.properties and schema.sql file for the application to work with a relational database initialised with a table and populated with some data.

The RESTful service implements 2 GET and 1 POST operations:

  • /customers – retrieves all customer data
  • /customer/:custId – returns a customer by customerId
  • /customer – adds a new customer

2.2 Interacting with the RESTful Service

The interaction with the RESTful service is show in the screenshot below:

FIS App Interactions

3 A Vert.x Implementation

Vert.x is neither an application nor a framework. It is a toolkit for building async applications. It is just a jar file and can be used by itself or with other frameworks. Vert.x is a polyglot event-driven toolkit supporting: Java, JavaScript, Groovy, JRuby, Ceylon, etc. However, this version of RHOAR only supports Java. The Vert.x architecture is depicted below:

Vert.x Architecture
Vert.x Architecture

Developers are used to using dependency injection in building testable and maintainable applications. Vert.x does not have built-in dependency injection although, being a toolkit or just a jar file, it can be used with other frameworks that provide dependency injection capabilities. The first frameworks that come to mind are Spring and Spring Boot. However, in my opinion, these frameworks and Vert.x appear to have an impedance mismatch. Vert.x is a light weight toolkit while Spring and Spring Boot are full stack frameworks/containers. Mixing them will make a Vert.x application not as light weight and as fast-starting as one would like. The alternative is to use Google Guice, a light weight dependency injection framework, which is already available as a plugin for Vert.x (not sure it is supported by Red Hat though). I am going to use Guice to provide the Dependency Injection capabilities for Vert.x. To include the plugin, we need to include the following dependency in the pom file.

<dependency>

 <groupId>com.englishtown.vertx</groupId>

 <artifactId>vertx-guice</artifactId>

 <version>2.3.1</version>

</dependency>

A Vert.x application usually consisted of 1 or more verticles. A verticle is a piece of code that can be deployed by Vert.x. And a Verticle is always executed on the same thread, called the Event Loop.

To implement the FIS application shown earlier, I used 2 standard verticles and a service.

In the FIS implementation, I used a relational database. Since we are developing microservices here, we should be able to choose whatever database that makes sense. In this case, I am using MongoDB as Vert.x provides an async MongoDB client which is a good fit for writing async applications. (Note that an async JDBC client is also available.)

The complete source code can be found here.

A portion of the code presented below is based on Bernard Tison’s excellent Appdev Microservices Development Lab.

The Vert.x application consists of a number of Java files which are described in subsequent sections.

3.1 Binder

Binding is similar to what Spring calls wiring. Bindings define how you are going to inject dependencies into your classes.

Binder is the Java class which extends the AbstractModule. It defines 2 providers: one for the MongoClient and one for the CustomerService using the @Provides and @Singleton annotations. I did not do anything in the configure method as the annotated methods already defined how dependencies are going to be injected.

public class Binder extends AbstractModule {


	@Provides @Singleton
    public MongoClient provideMongoClient(Vertx vertx, JsonObject config){
		System.out.println("Calling provideMongoClient...");
        return MongoClient.createShared(vertx, AppConfig.getInstance(vertx).getConfig());
    }
	
	@Provides @Singleton
    public CustomerService provideCustomerService(MongoClient client){
		System.out.println("Calling provideCustomerService...");
        return new CustomerServiceMongoImpl(client);
    }
	
	@Override
	protected void configure() {
		// TODO Auto-generated method stub

	}

}

3.1 StartUp

This is the main class that starts up our Vert.x application. To inform the Vert.x maven Plugin to use StartUp as the main class to start the application, include the “vertx.launcher “ property in the pom.xml as shown below.

<properties>

 ...

 <vertx.launcher>com.redhat.rhoar.customer.startup.StartUp</vertx.launcher>

</properties>

StartUp is a simple class, all it does is to create a non-clustered vertx instance using default options and to deploy the MainVerticle using the GuiceDeploymentHelper.

package com.redhat.rhoar.customer.startup;

import io.vertx.core.Vertx;

import io.vertx.core.json.JsonObject;

public class StartUp {

 public static void main(String[] args) {

  Vertx vertx = Vertx.vertx();

  GuiceDeploymentHelper deployer = new GuiceDeploymentHelper(vertx, new JsonObject(), Binder.class);

  deployer.deployVerticles(MainVerticle.class);

  deployer.coordinateFutures();

  }

 }

 

3.3 GuiceDeplymentHelper

GuiceDeploymentHelper is a convenience class that can be used to deploy one or more verticles. The things to note include:

  • adding the prefix “java-guice” to the verticle name to ensure that the GuiceVerticleFactory (part of the Vert.x Guice integration) creates a Guice Verticle,
  • adding a “guice_binder: BinderClassName key:/value pair in the configuration to inform Guice integration of our Binder class

 

After calling deployVerticle one of more times (depending on how many verticles you are to deploy), you have to call coordinateFutures to register a call back for verticle deployment completion or failure.

public GuiceDeploymentHelper(Vertx vertx, JsonObject config, Class binder) {

  this.vertx = vertx;

  config.put("guice_binder", binder.getName());

  options = new DeploymentOptions();

  options.setConfig(config);

}

public void deployVerticles(Class verticle) {

  Future<String> future = Future.future();

  futureList.add(future);

  String deploymentName = "java-guice:" + verticle.getName();

  vertx.deployVerticle(deploymentName, options, future.completer());

}

3.4 MainVerticle

This is the verticle that StartUp has been hardcoded to deploy. It responsibilities include:

  • Retrieving the configuration from a configMap when running in Openshift and saves it in the singleton AppConfig class
  • Deploying the RestVertical using the GuiceDeploymentHelper.
private void deploy(JsonObject config, Future<Void> startFuture) {

 

  GuiceDeploymentHelper deployer = new GuiceDeploymentHelper(vertx, config, Binder.class);

  deployer.deployVerticles(RestVerticle.class);

  deployer.coordinateFutures(startFuture);

}

3.5 RestVerticle

The RestVerticle extends AbstractVerticle and overrides its start method to:

  • Create a HTTP server to handle requests
  • Create a web router to handle several routes. The HTTP server passes an HTTP request to the first matching route. Each route has an associated handler that does the processing for the request
  • Implement handlers which call the CustomerService directly to interact with the MongoDB to save or retrieve customer info

In short, RestVerticle implements the RESTful service.

Note the @Inject annotation just before the RestVerticle constructor. We are using constructor injection here although Guice also supports field and setter injections.

@Inject

public RestVerticle(CustomerService customerService) {

  this.customerService = customerService;

}

(Please note that a Vert.x route is different from a Camel route. A Vert.x route is responsible for dispatching the HTTP request to the right handler while a Camel route contains the flow and integration logic.)

3.6 CustomerService

CustomerService is an interface which provides the following services:

public interface CustomerService {

  void getCustomers(Handler<AsyncResult<List<Customer>>> resulthandler);

  void getCustomer(String customerId, Handler<AsyncResult<Customer>> resulthandler);

  void addCustomer(Customer customer, Handler<AsyncResult<String>> resulthandler);

  void ping(Handler<AsyncResult<String>> resultHandler);

}

CustomerServiceMongoImpl.java is the implementation of the interface.

It uses a MongoClient’s find and save methods to interact with the MongoDB. The MongoClient is injected by Guice using constructor injection. Notice the @Inject annotation just before the CustomerServiceMongoImpl constructor.

@Inject

public CustomerServiceMongoImpl(MongoClient client) {

  this.client = client;

}

3.7 JUnit Tests

The JUnit tests include:

  • RestVerticleTest – uses VertxUnitRunner to run async tests to test out the RestVerticle logic using a mocked CustomerService implementation
  • CustomerServiceTest – uses VertxUnitRunner to run async tests to test out the Customer service logic using an embedded MongolDB

3.8 Deploying on Openshift

I am using “oc cluster up”’s all-in-one Openshift cluster for the deployment. Login to Openshift:

oc login -u developer https://10.0.2.15:8443

3.8.1 Deploying MongoDB

To deploy MongoDB, issue the following commands from a terminal from the directory where you cloned the project from Github:

export CUSTOMER_PRJ=vertx-guice-service

oc new-project $CUSTOMER_PRJ

oc process -f etc/customer-mongodb-persistent.yaml -p CUSTOMER_DB_USERNAME=mongo -p CUSTOMER_DB_PASSWORD=mongo -n $CUSTOMER_PRJ | oc create -f - -n $CUSTOMER_PRJ

oc policy add-role-to-user view -z default -n $CUSTOMER_PRJ

oc create configmap app-config --from-file=etc/app-config.yaml -n $CUSTOMER_PRJ

oc get configmap app-config -o yaml -n $CUSTOMER_PRJ

 

and wait for the deployment to finish. customer-mongodb-persistent.yaml is an Openshift template that also initialises the MongoDB with a couple of documents (the same 2 rows of data used in the FIS implementation)

app-config.yaml contains the properties such as port number and MongolDB connect strings and other info.

3.8.2 Deploying the Vert.x Application

To deploy the Vert.x application, you have to define the image stream for “redhat-openjdk18-openshift:1.1”image using the openjdk-image-stream.json. Issue the following commands from a terminal:

oc login -u system:admin https://10.0.2.15:8443

oc create -f etc/openjdk-image-stream.json -n openshift

oc login -u developer https://10.0.2.15:8443

mvn clean fabric8:deploy -Popenshift -Dfabric8.namespace=$CUSTOMER_PRJ

wait for the deployment to finish. Then issue:

export CUSTOMER_URL=http://$(oc get route vertx-simple-service -n $CUSTOMER_PRJ -o template –template='{{.spec.host}}')

curl -X GET "$CUSTOMER_URL/health/readiness"

curl -X GET "$CUSTOMER_URL/health/liveness"

3.9 Interacting Using REST Services

The interaction with the Vert.x application deployed on Openshift is captured in the following screenshot:

Vert.x App Interaction
Vert.x App Interaction

4 What Next?

For those who are familiar with Vert.x applications, you’d have noticed that the Vert.x application presented here does not use the EventBus. The event bus allows different verticles to communicate with each other irrespective of the language they are written in (polyglot environment). In the next installment, I shall describe how to convert the CustomerService into a separate verticle and let the RestVerticle communicate with it over the event bus. Instead of modifying the source files to use the event bus with additional boiler plate code, we can simply use code generation to generate a proxy to the CustomerService such that the code in RestVerticle does not need to be modified now that we have dependency injection working. It also hides the fact that they are actually communicating over the event bus. I shall also compare our simple application’s implementation using FIS and Vert.x. So, stay tuned