Table of Contents

Vert.x - Services Proxy

About

A service is just a functionality that simplify Vertx code by generating boilerplate code that will handle all event bus operations.

When creating a service there’s a certain amount of code to listen on the event bus for incoming messages, route them to the appropriate method and return results on the event bus. With Vert.x service proxies, that boilerplate code is generated and you can concentrate on writing the service.

To create a Vert.x service, you

Features

Code generation

Code generation happens through two annotation:

Example:

@ProxyGen // Generate service proxies
@VertxGen // Generate the clients
public interface SomeDatabaseService {
 // ...
}

Service helper classes

Service annotated with @ProxyGen annotation trigger the generation of the two service helper classes:

The service proxy mechanism relies on code generation, so modifications to the service interface require to re-compile the sources to regenerate the code.

Example:

Vertx Service Helper Class

Service client classes

With the @VertxGen annotation, you generate service stubs in any of the languages supported by Vert.x

For instance, you can write your service once in Java and interact with it with another language.

For this don’t forget to add the dependency on your language in your build descriptor.

Processor

How to configure the build tool to configure the annotation processing.

The process code can be found at this addresse CodeGenProcessor.java

Maven

See https://vertx.io/docs/guide-for-java-devs/#_maven_configuration_changes

Gradle

ext {
  vertxVersion = '3.8.4' 
}
dependencies {
    compile "io.vertx:vertx-core:$vertxVersion"
    compile "io.vertx:vertx-service-proxy:$vertxVersion:processor"
    compile "io.vertx:vertx-codegen:$vertxVersion:processor"
}
dependencies {
  implementation "io.vertx:vertx-core:$vertxVersion"
  implementation "io.vertx:vertx-service-proxy:$vertxVersion"
  compileOnly  "io.vertx:vertx-codegen:$vertxVersion"
  annotationProcessor "io.vertx:vertx-codegen:$vertxVersion:processor"
  // <3.8.4
  // annotationProcessor "io.vertx:vertx-service-proxy:$vertxVersion:processor"
  // >=3.8.4
  annotationProcessor "io.vertx:vertx-service-proxy:$vertxVersion"
}
task annotationProcessing(type: JavaCompile, group: 'build') { // codegen
  description 'Generates the stubs'
  options.incremental = false
  source = sourceSets.main.java
  classpath = configurations.compileClasspath
  destinationDir = project.file('src/main/generated')
  options.annotationProcessorPath = configurations.annotationProcessor
  options.debugOptions.debugLevel = "source,lines,vars"
  options.compilerArgs = [
    "-proc:only",
    "-processor", "io.vertx.codegen.CodeGenProcessor",
    "-Acodegen.output=${project.projectDir}/src/main"
  ]
}
compileJava {
  dependsOn (annotationProcessing)
}
sourceSets {
  main {
    java {
      srcDirs += 'src/main/generated'
    }
  }
}

If it's not working, there is also a Plugin

Processor Options

The processor is configured by a few options:

Compiler:

Management

Creation

@ProxyGen
public interface SomeDatabaseService {

 // A couple of static factory methods to create the instances (respectively implementation and proxy
 // Create is used when you deploy a vertical (ie you deploy the service)
 @GenIgnore
  static SomeDatabaseService create(arguments ..., Handler<AsyncResult<DatabaseServiceInterface>> readyHandler) {
    // The SomeDatabaseServiceImpl  class is an implementation class that you are creating
    return new SomeDatabaseServiceImpl(argument, readyHandler);
  }
 
 @GenIgnore
 // Proxy is used when you use/test the service as server client code
 static SomeDatabaseService createProxy(Vertx vertx, String address) {
   // The SomeDatabaseServiceVertxEBProxy class is generated by the annotation processor
   return new SomeDatabaseServiceVertxEBProxy(vertx, address);
 }

// Actual service operations here...
@Fluent
SomeDatabaseService doSomething(Arguments ..., Handler<AsyncResult<YourReturnType>> resultHandler);

}
@ModuleGen(name = "database", groupPackage = "net.bytle.api.db")
package net.bytle.api.db;

import io.vertx.codegen.annotations.ModuleGen;

Deploy

In the start method of a verticle, you will use the static factory create method of the interface to deploy your service (ie 1 service = 1 verticle)

SomeServiceInterface.create(argument, ready -> {
  if (ready.succeeded()) {
	// ready result is the implementation (ie SomeServiceInterfaceImpl instance)
	ServiceBinder binder = new ServiceBinder(vertx).setAddress(IP_QUEUE_NAME);
	binder.register(SomeServiceInterface.class, ready.result());
	promise.complete();
  } else {
	promise.fail(ready.cause());
  }
});

Call

The proxy is used in every server call to the service.

SomeDatabaseService service = SomeDatabaseService.createProxy(vertx,
    "database-service-address");

// Save some data in the database - this time using the proxy
service.doSomething(Arguments ..., res2 -> {
  if (res2.succeeded()) {
    // done
  }
});

Test

In a unit, you would:

@Before
  public void prepare(TestContext context) throws InterruptedException {
    vertx = Vertx.vertx();

    JsonObject conf = new JsonObject() 
      .put(DatabaseVerticle.KEY_JDBC_URL, "jdbc:hsqldb:mem:testdb;shutdown=true")
      .put(DatabaseVerticle.KEY_JDBC_MAX_POOL_SIZE, 4);

    vertx.deployVerticle(
      new DatabaseVerticle(), // The service
      new DeploymentOptions().setConfig(conf),
      context.asyncAssertSuccess(
        id -> { 
           // the deployment was successful, you get the deployment id (no needed)
           // but you can instantiate your service proxy
           service = DatabaseServiceInterface.createProxy(vertx, DatabaseVerticle.IP_QUEUE_NAME)) // The service proxy
        }
    );

  }

Documentation