RHOAR

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

 

Recap

In Part 1, I described the FIS-based microservice using a relational database and replicated its functionality using Vert.x, MongoDB with Guice dependency injection. The RestVerticle, which implements the RESTful microservice calls the CustomerService which retrieves/saves customer info to the MongoDB directly. In this article, I am going to show you how you can convert the CustomerService to run as a separate verticle called CustomerVerticle and have the RestVerticle communicate with the CustomerVerticle via the Vert.x EventBus. For a simple application such as this, there is no advantage to code the application this way. I am doing this to show you how this can be done as more complex Vert.x applications usually have multiple verticles running and communicating over the EventBus. To enable verticles to communicate over the EventBus, there is usually some boiler code needed to send messages and listen to messages over the EventBus. I am going to show you how to create a service proxy using code generation to obviate the need to write the boiler plate code yourself and convert the application developed in Part 1 to use the service proxy using dependency injection.

Service Proxy Generation

To generate a proxy for the CustomerService, we need to do the tasks described in the following sections.

Add Dependencies

Add the dependencies and plugin to the pom file.

<dependency>
<groupId>io.vertx</groupId>

<artifactId>vertx-codegen</artifactId>

</dependency>

<dependency>

<groupId>io.vertx</groupId>

<artifactId>vertx-service-proxy</artifactId>

</dependency>

<plugin>

<artifactId>maven-compiler-plugin</artifactId>

<version>3.1</version>

<configuration>

<annotationProcessors>

<annotationProcessor>io.vertx.codegen.CodeGenProcessor</annotationProcessor>

</annotationProcessors>

<generatedSourcesDirectory>

${project.basedir}/src/main/generated

</generatedSourcesDirectory>

</configuration>

</plugin>

Add @DataObject Annotation to Model

The info contained in the Customer Pojo will be moved back and forth the EventBus using JSON. We, the developer, do not want to write code to handle it ourselves. Instead, we want the code generation to take care of it. To do this, we need to add the @DataObject (from the vertx-codegen jar added as dependency) annotation to Customer. The Customer class must provide the following:

  • a constructor that takes JsonObject as a parameter
  • a toJson method that returns a JsonObject with the info contained in the Customer Pojo
    public Customer(JsonObject jsonObject) {
        customerId = jsonObject.getString("customerId");
        vipStatus = jsonObject.getString("vipStatus");
        balance = jsonObject.getInteger("balance");
    }

 

    public JsonObject toJson() {
        final JsonObject json = new JsonObject();
        json.put("customerId", customerId);
        json.put("vipStatus", vipStatus);
        json.put("balance", balance);
        return json;
    }

Vert.x code generation requires that all packages containing the @DataObject annotation be annotated with the @ModuleGen annotation. We achieve this by adding a package-info.java class in the package “com.redhat.rhoar.customer.model” as follows:

@io.vertx.codegen.annotations.ModuleGen(groupPackage = "com.redhat.rhoar.customer.model",

name = "rhoar-customer-model")
package com.redhat.rhoar.customer.model;

Add @ProxyGen Annotation to Service

To generate the EventBus proxy class for CustomerService, we need to annotate the service with the @ProxyGen annotation and add an Address as the destination for messages. The Address is just a string that uniquely identifies the destination.

@ProxyGen
public interface CustomerService {

 

    final public static String ADDRESS = "customer-service";

 

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

 

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

 

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

 

    void ping(Handler<AsyncResult> resultHandler);

 

}

Similar to @DataObject, we need to add a package-info.java class to the package “com.redhat.rhoar.customer.service” with content shown below:

@io.vertx.codegen.annotations.ModuleGen(groupPackage = "com.redhat.rhoar.customer.service",

name = "rhoar-customer-service")
package com.redhat.rhoar.customer.service;

 

Generate Service Proxy

To generate the service proxy, run the following command from the command prompt:

mvn clean package

If successful, you will find 2 generated classes in the “src/main/generated” directory:

  • CustomerServiceVertxEBProxy – a proxy class to be used by the client (RestVerticle)
  • CustomerServiceVertxProxyHandler – a helper class for registering the EventBus message handler for CustomerService used in CustomerVerticle

For Developer Studio to include the generated code in the build, you have to: right-click on the generated directory (from the Project Explorer pane) and select Build Path → Use As Source Folder.

Refactoring Code to use Service Proxy

Some existing Java classes require changes to use the service proxy. The changes are described in subsequent sections.

Modify Binder

We now have 2 implementations of CustomerService: CustomerServiceMongoImpl and CustomerServiceVertxEBProxy. We need to use the @Named annotation to differentiate them:

@Provides @Singleton
    @Named("mongo")
    public CustomerService provideCustomerService(MongoClient client){
        System.out.println("Calling provideCustomerService...");
        return new CustomerServiceMongoImpl(client);
    }

 

    @Provides @Singleton
    @Named("proxy")
    public CustomerService provideCustomerServiceProxy(Vertx vertx, String address) {
        System.out.println("Calling provideCustomerServiceProxy...");
        return new CustomerServiceVertxEBProxy(vertx, CustomerService.ADDRESS);
    }

Modify RestVerticle

The only change we make is to name the implementation “proxy” in the constructor using the @Named annotation:

@Inject

public RestVerticle(@Named(“proxy”) CustomerService customerService) {

this.customerService = customerService;

}

Add CustomerVerticle

We need to create a new class called CustomerVerticle:

public class CustomerVerticle extends AbstractVerticle {

 

    private CustomerService customerService;

 

    @Inject
    public CustomerVerticle(@Named("mongo") CustomerService customerService) {
        this.customerService = customerService;
    }
    
    @Override
    public void start(Future startFuture) throws Exception {
                
        //----
        // * Register the service on the event bus
        // * Complete the future
        //----
        ProxyHelper.registerService(CustomerService.class, vertx, customerService, CustomerService.ADDRESS);

 

        startFuture.complete();
    }

 

    @Override
    public void stop() throws Exception {
    }

 

}

Note that we use the @Named annotation to name the “mongo” implementation and that we use ProxyHelper.registerService to register the message handler on the EventBus for the CustomerService.

Modify MainVerticle

Now that we’ve added a new verticle, we have to deploy it in MainVerticle (either before or after RestVerticle):

deployer.deployVerticles(RestVerticle.class);

deployer.deployVerticles(CustomerVerticle.class);

Deploying to Openshift

We use the same commands to deploy to Openshift:

export CUSTOMER_PRJ=vertx-guice-service

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

You will see another service/route created in the $CUSTOMER_PRJ as depicted in the screenshot below:

Both Services Running
Both Services Running

If you examine the log of the newly running container of our modified app, you will see something like the following:

Startup Messages
Startup Messages

Note the 3 “Calling provideXXX…” messages which show the order of instantiation of dependencies by Guice.

Interacting with the Deployed App

The interaction with the app is captured in the screenshot below:

Service Interaction
Service Interaction

Comparing Vert.x to FIS for building Microservices

I am not going to benchmark the FIS and Vert.x implementations. There are lots of benchmark results comparing Spring Boot and Vert.x on the Internet. I am summarising my subjective observations below:

FIS Vert.x
Application Jar file size 32M 18.5M (No Service Proxy)20M (Service Proxy)
Difficulty Level Low – making use of Spring Boot magic including auto-configuration, etc. Medium – have to do everything ourselves. Once you learned how to program in vert.x, it is tedious rather than difficult. And it is certainly not as easy as Spring Boot.
Development Time Short – there is no Java code involved in this example, just the camel-context.xml and a couple of properties files. This is an exception rather than the norm though as most Camel apps usually involve coding as well. Longer – 10+ Java files for the Service Proxy version. The app does provide addition capability such as health and readiness checks for Openshift though.
Familiarity Most developers are comfortable with – implies that you can find Spring Boot developers easily Need to learn a new approach – implies that you may not be able to find good Vert.x developers easily
Threading Model Multiple threads Event-loop but can use WorkerVerticles ie, multiple threads if needed

 

Conclusions

Vert.x is not for everyone. Unless you have very specific use cases eg, building large scale, super scalable applications that don’t require complex business logic and efficient in use of resources (due to its threading model), you may want to stay with Spring Boot. The main argument for Vert.x is scalability and performance as it uses an event-loop instead of always using multiple threads. But if you have are using worker threads in your Vert.x application, then you may want to re-assess if they are nullifying the potential gains you are striving for using Vert.x. If you really want to, you can use Spring Boot with Vert.x. In my opinion, that combination may introduce impedance mismatch resulting in slower starting and fatter Vert.x applications. I say “may” because I don’t have facts or metrics to support that claim. Using Guice with Vert.x appears to make more sense if all you need is dependency injection. FIS which is Spring Boot-based, in my opinion, is the winner here as it is much easier to use than Vert.x and you can find good Spring Boot developers much easier than Vert.x developers.