When you compose a vert.x application, you may want to isolate a functionality somewhere and make it available to the rest of your application.

That’s the main purpose of service proxies. It lets you expose a service on the event bus, so, any other vert.x component can consume it, as soon as they know the address on which the service is published.

A service is described with a Java interface containing methods following the async pattern. Behind the hood, messages are sent on the event bus to invoke the service and get the response back. But to make it more easy to use for you, it generates a proxy that you can invoke directly (using the API from the service interface).

Using vert.x service proxies

To use the Vert.x Service Proxies, add the following dependency to the dependencies section of your build descriptor:

  • Maven (in your pom.xml):

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-service-proxy</artifactId>
  <version>3.2.0</version>
</dependency>
  • Gradle (in your build.gradle file):

compile 'io.vertx:vertx-service-proxy:3.2.0'

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

To generate the proxies in different languages, you will need to add the language dependency such as vertx-lang-groovy for Groovy…​.

Introduction to service proxies

Let’s have a look to service proxies and why they can be useful. Let’s imagine you have a database service exposed on the event bus, you should do something like this:

JsonObject message = new JsonObject();
message.put("collection", "mycollection")
    .put("document", new JsonObject().put("name", "tim"));
DeliveryOptions options = new DeliveryOptions().addHeader("action", "save");
vertx.eventBus().send("database-service-address", message, options, res2 -> {
  if (res2.succeeded()) {
    // done
  } else {
    // failure
  }
});

When creating a service there’s a certain amount of boilerplate 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, you can avoid writing all that boilerplate code and concentrate on writing your service.

You write your service as a Java interface and annotate it with the @ProxyGen annotation, for example:

@ProxyGen
public interface SomeDatabaseService {

  // A couple of factory methods to create an instance and a proxy
  static SomeDatabaseService create(Vertx vertx) {
    return new SomeDatabaseServiceImpl(vertx);
  }

  static SomeDatabaseService createProxy(Vertx vertx,
    String address) {
    return new SomeDatabaseServiceVertxEBProxy(vertx, address);
  }

 // Actual service operations here...
 void save(String collection, JsonObject document,
   Handler<AsyncResult<Void>> resultHandler);
}

Given the interface, Vert.x will generate all the boilerplate code required to access your service over the event bus, and it will also generate a client side proxy for your service, so your clients can use a rich idiomatic API for your service instead of having to manually craft event bus messages to send. The client side proxy will work irrespective of where your service actually lives on the event bus (potentially on a different machine).

That means you can interact with your service like this:

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

// Save some data in the database - this time using the proxy
service.save("mycollection", new JsonObject().put("name", "tim"), res2 -> {
  if (res2.succeeded()) {
    // done
  }
});

You can also combine @ProxyGen with language API code generation (@VertxGen) in order to create service stubs in any of the languages supported by Vert.x - this means you can write your service once in Java and interact with it through an idiomatic other language API irrespective of whether the service lives locally or is somewhere else on the event bus entirely. For this don’t forget to add the dependency on your language in your build descriptor:

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

Async Interface

To be used by the service-proxy generation, the service interface must comply to a couple of rules. First it should follow the async pattern. To return result, the method should declare a Handler<AsyncResult<ResultType>>. ResultType can be another proxy (and so a proxies can be factories for other proxies.

Let’s see an example

@ProxyGen
public interface SomeDatabaseService {

 // A couple of factory methods to create an instance and a proxy

 static SomeDatabaseService create(Vertx vertx) {
   return new SomeDatabaseServiceImpl(vertx);
 }

 static SomeDatabaseService createProxy(Vertx vertx, String address) {
   return new SomeDatabaseServiceVertxEBProxy(vertx, address);
 }

 // A method notifying the completion without a result (void)
 void save(String collection, JsonObject document,
   Handler<AsyncResult<Void>> result);

 // A method providing a result (a json object)
 void findOne(String collection, JsonObject query,
   Handler<AsyncResult<JsonObject>> result);

 // Create a connection
 void createConnection(String shoeSize,
   Handler<AsyncResult<MyDatabaseConnection>> resultHandler);

}

with:

@ProxyGen
@VertxGen
public interface MyDatabaseConnection {

 void insert(JsonObject someData);

 void commit(Handler<AsyncResult<Void>> resultHandler);

 @ProxyClose
 void close();
}

You can also declare that a particular method unregisters the proxy by annotating it with the @ProxyClose annotation. The proxy instance is disposed when this method is called.

More constraints on the service interfaces are described below.

Exposing your service

Once you have your service interface, compile the source to generate the stub and proxies. Then, you need some code to "register" your service on the event bus:

SomeDatabaseService service = new SomeDatabaseServiceImpl();
// Register the handler
ProxyHelper.registerService(SomeDatabaseService.class, vertx, service,
    "database-service-address");

This can be done in a verticle, or anywhere in your code.

Once registered, the service becomes accessible. If you are running your application on a cluster, the service is available from any host.

Proxy creation

Now that the service is exposed, you probably want to consume it. For this, you need to create a proxy. The proxy can be created using the ProxyHelper class:

SomeDatabaseService service = ProxyHelper.createProxy(SomeDatabaseService.class,
    vertx,
    "database-service-address");
// or with delivery options:
SomeDatabaseService service2 = ProxyHelper.createProxy(SomeDatabaseService.class,
    vertx,
    "database-service-address", options);

The second method takes an instance of DeliveryOptions where you con configure the message delivery (such as the timeout).

Alternatively, you can use the generated proxy class. The proxy class name is the service interface class name followed by VertxEBProxy. For instance, if your service interface is named SomeDatabaseService, the proxy class is named SomeDatabaseServiceVertxEBProxy.

Generally, service interface contains a createProxy static method to create the proxy. But this is not required:

@ProxyGen
public interface SomeDatabaseService {

 // Method to create the proxy.
 static SomeDatabaseService createProxy(Vertx vertx, String address) {
   return new SomeDatabaseServiceVertxEBProxy(vertx, address);
 }

 // ...
}

Consuming your service from a browser or from Node.js

The previous section has shown how you can create a service proxy in Java. However, you can consume your service directly from your browser or from a node.js application using a SockJS-based proxy.

First, you need to configure the SockJS bridge, in order to let the proxy communicate with the service. You will find more details about the SockJS bridge in <a href="http://vertx.io/docs/vertx-web/java/#_sockjs_event_bus_bridge">vertx-web</a>:

SomeDatabaseService service = new SomeDatabaseServiceImpl();
ProxyHelper.registerService(SomeDatabaseService.class, vertx, service,
    "database-service-address");


Router router = Router.router(vertx);
// Allow events for the designated addresses in/out of the event bus bridge
BridgeOptions opts = new BridgeOptions()
    .addInboundPermitted(new PermittedOptions()
        .setAddress("database-service-address"))
    .addOutboundPermitted(new PermittedOptions()
        .setAddress("database-service-address"));

// Create the event bus bridge and add it to the router.
SockJSHandler ebHandler = SockJSHandler.create(vertx).bridge(opts);
router.route("/eventbus/*").handler(ebHandler);

vertx.createHttpServer().requestHandler(router::accept).listen(8080);

Once you have the sockJS bridge configured, other applications developed in JavaScript can interact with your service directly. During the service compilation, a JS proxy module is generated, and is named as follows: module_name-js/server-interface_simple_name + -proxy.js. So for instance, if your interface is named MyService, the proxy module is named my_service-proxy.js. Again, this proxy is usable from your browser or from node.js.

The generated proxy is a JavaScript module compatible with CommonJS, AMD and Webpack. The proxy then just needs to instantiated with the EventBus bridge and the service EventBus address:

<script src="http://cdn.sockjs.org/sockjs-0.3.4.min.js"></script>
<script src="vertx-eventbus.js"></script>
<script>
  var eb = new EventBus('http://localhost:8080/eventbus');
  eb.onopen = function() {
    var SomeDatabaseService =
      require('vertx-database-js/some_database_service-proxy.js');
    var svc = new SomeDatabaseService(eb, "database-service-address");
  };
</script>

Restrictions for service interface

There are restrictions on the types and return values that can be used in a service method so that these are easy to marshall over event bus messages and so they can be used asynchronously. They are:

Return types

Must be one of

  • void

  • @Fluent and return reference to the service (this):

@Fluent
SomeDatabaseService doSomething();

This is because methods must not block and it’s not possible to return a result immediately without blocking if the service is remote.

Parameter types

Let JSON = JsonObject | JsonArray Let PRIMITIVE = Any primitive type or boxed primitive type

Parameters can be any of:

  • JSON

  • PRIMITIVE

  • List<JSON>

  • List<PRIMITIVE>

  • Set<JSON>

  • Set<PRIMITIVE>

  • Map<String, JSON>

  • Map<String, PRIMITIVE>

  • Any Enum type

  • Any class annotated with @DataObject

If an asynchronous result is required a last parameter of type Handler<AsyncResult<R>> can be provided.

R can be any of:

  • JSON

  • PRIMITIVE

  • List<JSON>

  • List<PRIMITIVE>

  • Set<JSON>

  • Set<PRIMITIVE>

  • Any Enum type

  • Any class annotated with @DataObject

  • Another proxy

Overloaded methods

There must be no overloaded service methods. (i.e. more than one with the same name, regardless the signature).

Convention for invoking services over the event bus (without proxies)

Service Proxies assume that event bus messages follow a certain format so they can be used to invoke services.

Of course, you don’t have to use client proxies to access remote service if you don’t want to. It’s perfectly acceptable to interact with them by just sending messages over the event bus.

In order for services to be interacted with a consistent way the following message formats must be used for any Vert.x services.

The format is very simple:

  • There should be a header called action which gives the name of the action to perform.

  • The body of the message should be a JsonObject, there should be one field in the object for each argument needed by the action.

For example to invoke an action called save which expects a String collection and a JsonObject document:

Headers:
    "action": "save"
Body:
    {
        "collection", "mycollection",
        "document", {
            "name": "tim"
        }
    }

The above convention should be used whether or not service proxies are used to create services, as it allows services to be interacted with consistently.

In the case where service proxies are used the "action" value should map to the name of an action method in the service interface and each [key, value] in the body should map to a [arg_name, arg_value] in the action method.

For return values the service should use the message.reply(…​) method to send back a return value - this can be of any type supported by the event bus. To signal a failure the method message.fail(…​) should be used.

If you are using service proxies the generated code will handle this for you automatically.