Vertx platform / Microservices / node integration via event bus

615 views
Skip to first unread message

Rick Hight

unread,
Jan 18, 2016, 3:23:03 PM1/18/16
to vert.x
Working on a set of best practices to create services in Node and Vertx and get them to communicate via the Vertx event bus and later possibly Vertx Service Factories and Vertx Service Proxies. 

The idea is to define a microservices platform / build pipleline. 

Along these lines, we setup a series of setups and guides as we learn / investigate and iterate. 


The goals change a bit as we iterate. So far we have node setup to talk to a sample app written in Vertx.

Feedback, help, guidance, experience, advice, is welcome.

Vert.x Node EventBus exampling using Kotlin which will be deployed on EC2 with Otto

The purpose of this example is to show how to connect Node and Vert.x with the Vert.x EventBususing the Vert.x event bus bridge. We will use Java on Kotlin in this example. We also plan on using Vert.x built-in clustering and discovery in EC2, which means we will need to configureHazelcast to work in EC2. We plan on using gradle for the build.

Another feature of Vert.x 3 that we want to use is Service Proxies and Service Factories. This should allow us to create client stubs in JS which will allows us to make async call services written in Vert.x/Java/Kotlin from JavaScript/Node (or for that matter JRubyRubyJython,Python, etc.) via the event bus and event bus bridge.

Based on research, this should be possible. Some of the research were slides in talks we saw on youtube.com and Vert.x docs. We have worked with Vert.x and the EventBus before.

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. --Vert.x docs

We may have to break this up in small steps.

Calling a proxy from JS in a browser from Vert.x docs

<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>

Arguments to services must be basic types and data objects. Data objects are fairly easy to create see above link.

From Vert.x docs allowed types in services

Parameters
JSON

PRIMITIVE

List<JSON>

List<PRIMITIVE>

Set<JSON>

Set<PRIMITIVE>

Map<String, JSON>

Map<String, PRIMITIVE>

Any Enum type

Any class annotated with @DataObject

Return type
JSON

PRIMITIVE

List<JSON>

List<PRIMITIVE>

Set<JSON>

Set<PRIMITIVE>

Any Enum type

Any class annotated with @DataObject

Another proxy

Why Kotlin?

We picked Kotlin because it is strongly typed, compiles fast and has great Java integration. It provides many of the things we love about Scala but in a more Java compatible way. At a minimum we will use Kotlin data classes. Kotlin is also similar to Swift (Swift is Kotlin). Kotlin syntax is also a lot closer to JavaScript than Java, but still strongly typed and able to take full advantage of the JDK and Java libs. Kotlin has constructs found in Swift and Scala, and is very approachable.

Why Vert.x?

Vert.x is reactive and fast. It has a lot of integration with monitoring, service discovery, and reactive streaming. It also allows more flexibility in how we manage our IO/Threads, i.e., pure reactive or multi-reactive. The idea is to write microservices in Node and Vert.x

Why Node?

A lot of companies use Node and we often find ourselves integrating with Node application.

Why Gradle?

It is less verbose than Maven and more programmable. It is easier to hack.



Step 1 Initially setting up gradle and create Vert.x hello world



Initially setting up gradle and vert.x.

We will expand in this script, but it is easier to understand in its infancy.

We are using John Rengelman's Shadow Jar Plugin which is similar to the Maven which is similar to Maven Shade plugin but for Gradle and is faster than the Gradle built-in fat jar.

I am not sure how we will do the final deploy. This is a step down the road. The direction might change.

At this point the gradle build file is pretty basic.

Gradle build file

plugins {
    id 'java'
    id 'application'
    id 'com.github.johnrengelman.shadow' version '1.2.2'
    id 'idea'
}

group 'rickhigh'
version '1.0-SNAPSHOT'


idea {
    project {
        languageLevel = '1.8'
    }
}
repositories {
    mavenCentral()
    maven {
        url = 'http://oss.sonatype.org/content/repositories/snapshots/'
    }
    mavenLocal() /* Just in case we want to use local artifacts that we build locally. */
}


sourceCompatibility = '1.8'
mainClassName = 'io.vertx.core.Launcher'

dependencies {
    compile "io.vertx:vertx-core:3.2.0"
    testCompile group: 'junit', name: 'junit', version: '4.11'
}

/* used to create the fat jar files. */
shadowJar {
    classifier = 'fat'
    manifest {
        attributes 'Main-Verticle': 'com.github.vertx.node.example.HelloWorldVerticle'
    }
    mergeServiceFiles {
        include 'META-INF/services/io.vertx.core.spi.VerticleFactory'
    }
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.9'
}

You can see this in this branch.

Like I said, I am not sure if we will stick with gradle or use maven. It all depends. Also I am not sure we will use fatjars or dist. Or run with vertx command line.

For now, we are doing this.

Notice that the main app is io.vertx.core.Launcher. Also notice that our Verticle is calledcom.github.vertx.node.example.HelloWorldVerticle. This will change in a later branch.

HelloWorldVerticle

package com.github.vertx.node.example;

import io.vertx.core.AbstractVerticle;

public class HelloWorldVerticle extends AbstractVerticle {

    @Override
    public void start() {
        vertx.createHttpServer().requestHandler(req -> req.response().end("Hello World!")).listen(8080);
    }
}

To build the shadow jar / fat jar do this.

Build the fat jar

$ ./gradlew shadowJar

To run the verticle do this:

Run the verticle

$ #FIND The JAR
$ find . -name "*fat.jar"
./build/libs/vertx-node-ec2-eventbus-example-1.0-SNAPSHOT-fat.jar

$ # Run the jar
$ java -jar ./build/libs/vertx-node-ec2-eventbus-example-1.0-SNAPSHOT-fat.jar

Files so far.


├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── settings.gradle
├── src
    ├── main
    │   └── java
    │       └── com
    │           └── github
    │               └── vertx
    │                   └── node
    │                       └── example
    │                           └── HelloWorldVerticle.java
    └── test
        └── java

You can see this in this branch.

After you run this example, you can see it in action as follows:

Curl it

$ curl http://localhost:8080
Hello World!


Step 2 Change Hello World to use EventBus


Now we want to deploy to verticles running in the same JVM. The first verticle is an HTTP verticle and it will send a Message on the Vert.x EventBus.

Now we are going to change things up a bit. We will have a main verticle called MainVerticlewhich will start up two verticles, namely, HelloWorldVerticle and WebVerticle.

You can find the source for this in this branch.

We also setup logging.

One of the biggest mistakes is not logging errors in an async program.

The HelloWorldVerticle handles events from the event bus, and responds.

Change HelloWorldVerticle to use event bus instead of HTTP Server

package com.github.vertx.node.example;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.eventbus.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorldVerticle extends AbstractVerticle {

    private final Logger logger = LoggerFactory.getLogger(HelloWorldVerticle.class);
    @Override
    public void start() {
        vertx.eventBus().consumer(Services.HELLO_WORLD.toString(), message -> {
            dispatchMessage(message);
        });
    }

    private void dispatchMessage(final Message<Object> message) {

        try {
            final HelloWorldOperations operation = HelloWorldOperations.valueOf(message.body().toString());
            switch (operation) {
                case SAY_HELLO_WORLD:
                    message.reply("HELLO WORLD");
                    break;
                default:
                    logger.error("Unable to handle operation {}", operation);
                    message.reply("Unsupported operation");
            }
        }catch (final Exception ex) {
            logger.error("Unable to handle operation due to exception" + message.body(), ex);
        }
    }

}

The WebVerticle handles all HTTP request by delegating to the HelloWorldVerticle.

Create WebVerticle to send a hello world request message to the HelloVerticle

package com.github.vertx.node.example;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.http.HttpServerRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class WebVerticle extends AbstractVerticle {


    private final Logger logger = LoggerFactory.getLogger(WebVerticle.class);

    @Override
    public void start() {
        vertx.createHttpServer()
                .requestHandler(httpRequest -> handleHttpRequest(httpRequest) )
                .listen(8080);
    }

    private void handleHttpRequest(final HttpServerRequest httpRequest) {

        /* Invoke using the event bus. */
        vertx.eventBus().send(Services.HELLO_WORLD.toString(),
                HelloWorldOperations.SAY_HELLO_WORLD.toString(), response -> {

            if (response.succeeded()) {
                /* Send the result from HelloWorldService to the http connection. */
                httpRequest.response().end(response.result().body().toString());
            } else {
                logger.error("Can't send message to hello service", response.cause());
                httpRequest.response().setStatusCode(500).end(response.cause().getMessage());
            }
        });
    }
}

We define some constants via Enums to operations and such.

Enum for service names and operations

package com.github.vertx.node.example;

public enum Services {

    HELLO_WORLD
}

package com.github.vertx.node.example;

public enum HelloWorldOperations {

    SAY_HELLO_WORLD
}

The MainVerticle starts up the other two verticles, and then uses a vertx timer to see when the operation completes.

Note we added a main method so it would be easy to startup in our IDE.

Define a verticle to start the other two.

package com.github.vertx.node.example;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Vertx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class MainVerticle extends AbstractVerticle {



    private final Logger logger = LoggerFactory.getLogger(MainVerticle.class);

    @Override
    public void start() throws InterruptedException {

        /** Count of services. */
        final AtomicInteger serviceCount = new AtomicInteger();

        /** List of verticles that we are starting. */
        final List<AbstractVerticle> verticles = Arrays.asList(new HelloWorldVerticle(), new WebVerticle());

        verticles.stream().forEach(verticle -> vertx.deployVerticle(verticle, deployResponse -> {

            if (deployResponse.failed()) {
                logger.error("Unable to deploy verticle " + verticle.getClass().getSimpleName(),
                        deployResponse.cause());
            } else {
                logger.info(verticle.getClass().getSimpleName() + " deployed");
                serviceCount.incrementAndGet();
            }
        }));


        /** Wake up in five seconds and check to see if we are deployed if not complain. */
        vertx.setTimer(TimeUnit.SECONDS.toMillis(5), event -> {

            if (serviceCount.get() != verticles.size()) {
                logger.error("Main Verticle was unable to start child verticles");
            } else {
                logger.info("Start up successful");
            }
        });

    }


    public static void main(final String... args) {
        final Vertx vertx = Vertx.vertx();
        vertx.deployVerticle(new MainVerticle());
    }
}

We added logging to the gradle build file as well as a logback.xml file to the test resources.

In addition to adding logging, we changed the main verticle to MainVerticle fromHelloWorldVerticle.

Updated gradle file

plugins {
    id 'java'
    id 'application'
    id 'com.github.johnrengelman.shadow' version '1.2.2'
    id 'idea'
}

group 'rickhigh'
version '1.0-SNAPSHOT'


idea {
    project {
        languageLevel = '1.8'
    }
}
repositories {
    mavenCentral()
    maven {
        url = 'http://oss.sonatype.org/content/repositories/snapshots/'
    }
    mavenLocal() /* Just in case we want to use local artifacts that we build locally. */
}


sourceCompatibility = '1.8'
mainClassName = 'io.vertx.core.Launcher'

dependencies {
    compile "io.vertx:vertx-core:3.2.0"
    compile 'ch.qos.logback:logback-core:1.1.3'
    compile 'ch.qos.logback:logback-classic:1.1.3'
    compile 'org.slf4j:slf4j-api:1.7.12'
    testCompile group: 'junit', name: 'junit', version: '4.11'
}

/* used to create the fat jar files. */
shadowJar {
    classifier = 'fat'
    manifest {
        attributes 'Main-Verticle': 'com.github.vertx.node.example.MainVerticle'
    }
    mergeServiceFiles {
        include 'META-INF/services/io.vertx.core.spi.VerticleFactory'
    }
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.9'
}

Notice the main veritcle changed

shadowJar {
    classifier = 'fat'
    manifest {
        attributes 'Main-Verticle': 'com.github.vertx.node.example.MainVerticle'
    }
    mergeServiceFiles {
        include 'META-INF/services/io.vertx.core.spi.VerticleFactory'
    }
}


Step 3 Adding Kotlin


We decided to add Kotlin to the mix. It is terse and modern and very compatible with the core Java libs. It builds on your knowledge of Java. The Idea guys explain how to do add Kotlin to a Gradle project, yet it still took some messing around.

You can find the source code for this in this branch.

To do this we added the Kotlin plugin to gradle.

Adding Kotlin to gradle

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.0.0-beta-4584'
    }
}


plugins {
    id 'java'
    id 'application'
    id 'com.github.johnrengelman.shadow' version '1.2.2'
    id 'idea'
}


apply plugin: "kotlin"

...

dependencies {
...
    compile 'org.slf4j:slf4j-api:1.7.12'
    compile 'org.jetbrains.kotlin:kotlin-stdlib:1.0.0-beta-4584'
    testCompile group: 'junit', name: 'junit', version: '4.11'
}


Most of the gradle build files stays the same. The way we build and execute stays the same as well.

We left WebVerticle in Java for now. We changed the other files and we were able to get rid of a few files as well by grouping enums with the classes that more or less own them.

HelloWorldService.kt

package com.github.vertx.node.example

import io.vertx.core.AbstractVerticle
import io.vertx.core.eventbus.Message
import org.slf4j.LoggerFactory

enum class HelloWorldOperations {

    SAY_HELLO_WORLD
}


class HelloWorldVerticle : AbstractVerticle() {

    private val logger = LoggerFactory.getLogger(HelloWorldVerticle::class.java)
    override fun start() {
        vertx.eventBus().consumer<Any>(Services.HELLO_WORLD.toString()) { message -> dispatchMessage(message) }
    }

    private fun dispatchMessage(message: Message<Any>) {

        try {
            val operation = HelloWorldOperations.valueOf(message.body().toString())
            when (operation) {
                HelloWorldOperations.SAY_HELLO_WORLD -> message.reply("HELLO WORLD FROM KOTLIN")
                else -> {
                    logger.error("Unable to handle operation {}", operation)
                    message.reply("Unsupported operation")
                }
            }
        } catch (ex: Exception) {
            logger.error("Unable to handle operation due to exception" + message.body(), ex)
        }

    }

}

MainService.kt

package com.github.vertx.node.example

import io.vertx.core.AbstractVerticle
import io.vertx.core.Vertx
import org.slf4j.LoggerFactory
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import kotlin.collections.forEach

enum class Services {
    HELLO_WORLD
}


public class MainVerticle : AbstractVerticle() {


    private val logger = LoggerFactory.getLogger(MainVerticle::class.java)

    override fun start() {

        /** Count of services.  */
        val serviceCount = AtomicInteger()

        /** List of verticles that we are starting.  */
        val verticles = Arrays.asList(HelloWorldVerticle(), WebVerticle())

        verticles.forEach { verticle->
            vertx.deployVerticle(verticle) { deployResponse ->

                if (deployResponse.failed()) {
                    logger.error("Unable to deploy verticle ${verticle.javaClass.simpleName}",
                            deployResponse.cause())
                } else {
                    logger.info("${verticle.javaClass.simpleName} deployed")
                    serviceCount.incrementAndGet()
                }
            }
        }


        /** Wake up in five seconds and check to see if we are deployed if not complain.  */
        vertx.setTimer(TimeUnit.SECONDS.toMillis(5)) { event ->

            if (serviceCount.get() != verticles.size) {
                logger.error("Main Verticle was unable to start child verticles")
            } else {
                logger.info("Start up successful")
            }
        }

    }

    companion object {
        @JvmStatic fun main(args: Array<String>) {
            val vertx = Vertx.vertx()
            vertx.deployVerticle(MainVerticle())
        }
    }
}

The code is a bit smaller and there are less files. We think Kotlin will be a big boon when we start using data class.


Step 4 Call Vertx app from event bus from Node



You can find the steps for step 4 in this branch.

Install node

If you don't have node setup, then now is a good time.

$ brew install node

Output

==> Reinstalling node
==> Downloading https://homebrew.bintray.com/bottles/node-5.3.0.el_capitan.bottle.tar.gz
Already downloaded: /Library/Caches/Homebrew/node-5.3.0.el_capitan.bottle.tar.gz
==> Pouring node-5.3.0.el_capitan.bottle.tar.gz
...
==> Summary
🍺  /usr/local/Cellar/node/5.3.0: 2827 files, 37M
v5.3.0

Verify

$ node -v

$ npm -v

You should see later versions of both projects.

Create a project folder and navigate to it. We put all node files in {root_project}/node and we put all vertx files in {root_project}/vertx.

Init project

$ npm init

Install http

$ npm install http --save

package.json

{
  "name": "node-2-vertx",
  "version": "1.0.0",
  "description": "Call Vertx",
  "main": "run.js",
  "scripts": {
    "test": "test"
  },
  "author": "Rick Hightower, Geoff Chandler",
  "license": "ISC",
  "dependencies": {
    "http": "0.0.0"
  }
}

Just to see if we have everything setup.

Call vertx service via HTTP

var http = require('http');

var options = {
  host: 'localhost',
  path: '/hello',
  port: 8080
};

callback = function(response) {
  var str = '';

  //another chunk of data has been received, so append it to `str`
  response.on('data', function (chunk) {
    str += chunk;
  });

  //the whole response has been received, so we just print it out here
  response.on('end', function () {
    console.log("FROM SERVER " + str);
  });
}

http.request(options, callback).end();

Next up, Let's use the event bus from this NPM module vertx3 NPM module.

Adding dependency for vertx event bus

$ npm install vertx3-eventbus-client  --save
$ npm install sockjs-client  --save

This will add the dependencies to your package.json file in your node folder.

Let's review the Java code which calls the bus and let's look at the code to install the vertx bridge. First we need to install vertx-web support, and then we need the SockJS bridge support. We will also install the TCP event bus bridge so we can call this microservice from not only Node but other Vertx/Java microservices.

Gradle build script build.gradle

dependencies {
    compile "io.vertx:vertx-core:3.2.0"
    ...
    compile 'io.vertx:vertx-tcp-eventbus-bridge:3.2.0' // ** ADDED
    compile 'io.vertx:vertx-web:3.2.0'                 //** ADDED
    testCompile group: 'junit', name: 'junit', version: '4.11'
}

Next we need to configure the event bus bridge. We can do this in the WebVerticle as follows.

WebVerticle.java configure event bus sockJS bridge

package com.github.vertx.node.example;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.sockjs.BridgeOptions;
import io.vertx.ext.web.handler.sockjs.PermittedOptions;
import io.vertx.ext.web.handler.sockjs.SockJSHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This gets started by the MainVerticle.
 * It configures the event bus bridge and install the REST routes.
 */
public class WebVerticle extends AbstractVerticle {

    private final Logger logger = LoggerFactory.getLogger(WebVerticle.class);

    @Override
    public void start() {

        /* Create vertx web router. */
        final Router router = Router.router(vertx);


        /* Install our original "REST" handler at the /hello/ uri. */
        router.route("/hello/*").handler(event -> handleHttpRequestToHelloWorld(event.request()));


        /* Allow Hello World service to be exposed to Node.js. */
        final BridgeOptions options = new BridgeOptions()
                .addInboundPermitted(
                        new PermittedOptions().setAddress(Services.HELLO_WORLD.toString()));

        /* Configure bridge at this HTTP/WebSocket URI. */
        router.route("/eventbus/*").handler(SockJSHandler.create(vertx).bridge(options));

        /* Install router into vertx. */
        vertx.createHttpServer()
                .requestHandler(router::accept)
                .listen(8080);
    }

    /** This REST endpoint if for hello.
     *  It invokes the hello world service via the event bus.
     * @param httpRequest HTTP request from vertx.
     */
    private void handleHttpRequestToHelloWorld(final HttpServerRequest httpRequest) {

        /* Invoke using the event bus. */
        vertx.eventBus().send(Services.HELLO_WORLD.toString(),
                HelloWorldOperations.SAY_HELLO_WORLD.toString(), response -> {

           /* If the response was successful, this means we were able to execute the operation on
              the HelloWorld service.
              Pass the results to the http request's response.
           */
            if (response.succeeded()) {
                /* Send the result to the http connection. */
                logger.debug("Successfully invoked HelloWorld service {}", response.result().body());
                httpRequest.response().end(response.result().body().toString());
            } else {
                logger.error("Can't send message to hello world service", response.cause());
                //noinspection ThrowableResultOfMethodCallIgnored
                httpRequest.response().setStatusCode(500).end(response.cause().getMessage());
            }
        });
    }
}

Notice that we had to add the event topic new PermittedOptions().setAddress(Services.HELLO_WORLD.toString() to expose it to the outside world (in this case the Node.js world).

HelloWorldService.kt and MainService.kt stay the same. (Kotlin files). We will include them at the end with some extra comments. Now we can change our node.js example to use the event bus bridge.

Change node to use event bus to call hello world service instead of REST

var EventBus = require('vertx3-eventbus-client');
var eventBus = new EventBus("http://localhost:8080/eventbus/");

/** Don't call until the event bus is open. */
function onopenEventBus() {

      //Call using event bus.
      eventBus.send("HELLO_WORLD",
              "SAY_HELLO_WORLD", function(response, json) {
              console.log(json.body);
      });
}

/** Get notified of errors. */
function onerrorEventBus(error) {
  console.log("Problem calling event bus " + error)
}


eventBus.onopen = onopenEventBus;
eventBus.onerror = onerrorEventBus;

For completeness here is the Kotlin files up to this point:

MainService.kt

package com.github.vertx.node.example

import io.vertx.core.AbstractVerticle
import io.vertx.core.Vertx
import org.slf4j.LoggerFactory
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import kotlin.collections.forEach

enum class Services {
    HELLO_WORLD
}

public class MainVerticle : AbstractVerticle() {

    private val logger = LoggerFactory.getLogger(MainVerticle::class.java)

    override fun start() {

        /** Count of services.  */
        val serviceCount = AtomicInteger()

        /** List of verticles that we are starting.  */
        val verticles = Arrays.asList(HelloWorldVerticle(), WebVerticle())


        verticles.forEach { verticle ->
            vertx.deployVerticle(verticle) { deployResponse ->

                if (deployResponse.failed()) {
                    logger.error("Unable to deploy verticle ${verticle.javaClass.simpleName}",
                            deployResponse.cause())
                } else {
                    logger.info("${verticle.javaClass.simpleName} deployed")
                    serviceCount.incrementAndGet()
                }
            }
        }

        /** Wake up in five seconds and check to see if we are deployed if not complain.  */
        vertx.setTimer(TimeUnit.SECONDS.toMillis(5)) { event ->

            if (serviceCount.get() != verticles.size) {
                logger.error("Main Verticle was unable to start child verticles")
            } else {
                logger.info("Start up successful")
            }
        }

    }

}

fun main(args: Array<String>) {
    val vertx = Vertx.vertx()
    vertx.deployVerticle(MainVerticle())
}

HelloWorldService.kt

package com.github.vertx.node.example

import io.vertx.core.AbstractVerticle
import io.vertx.core.eventbus.Message
import org.slf4j.LoggerFactory

enum class HelloWorldOperations {

    SAY_HELLO_WORLD
}


/** Hello World Verticle gets started by Main Verticle.
 * Listens to the event bus.
 */
class HelloWorldVerticle : AbstractVerticle() {

    private val logger = LoggerFactory.getLogger(HelloWorldVerticle::class.java)

    override fun start() {
        vertx.eventBus().consumer<Any>(Services.HELLO_WORLD.toString()) { message -> dispatchMessage(message) }
    }

    /**
     * Handles message from the event bus.
     */
    private fun dispatchMessage(message: Message<Any>) {

        try {
            val operation = HelloWorldOperations.valueOf(message.body().toString())

            /** Switch statement that handles various operations. */
            when (operation) {
                HelloWorldOperations.SAY_HELLO_WORLD -> message.reply("HELLO WORLD FROM KOTLIN")
                else -> {
                    logger.error("Unable to handle operation {}", operation)
                    message.reply("Unsupported operation")
                }
            }
        } catch (ex: Exception) {
            logger.error("Unable to handle operation due to exception" + message.body(), ex)
        }
    }

}

Arnaud Estève

unread,
Jan 18, 2016, 5:48:13 PM1/18/16
to vert.x
Thanks for the example, and nice to see Kotlin in action :)


May a suggest a couple of changes (from a very quick first read) ? 

Is there a reason why you're watching every 5ms the deployment status ? Instead of just using the start(Future<Void>) and completing the future once every deployment has succeeded ? (to illustrate timers, maybe)

And also I think you could use a method reference here : requestHandler(httpRequest -> handleHttpRequest(httpRequest) ) .requestHandler(this::handleHttpRequest)
Which is more concise, more readable imho.

Thanks again, every example is interesting to read. 
I'll read abit more in depth once I'll get some sleep.

Rick Hight

unread,
Jan 18, 2016, 11:43:37 PM1/18/16
to vert.x
inline


On Monday, January 18, 2016 at 2:48:13 PM UTC-8, Arnaud Estève wrote:
Thanks for the example, and nice to see Kotlin in action :)


May a suggest a couple of changes (from a very quick first read) ? 

Is there a reason why you're watching every 5ms the deployment status ? Instead of just using the start(Future<Void>) and completing the future once every deployment has succeeded ? (to illustrate timers, maybe)

yes.. I have N verticles. In this case 2, I want to know when both of them started not just one. 
If one of them fails, (there will be more.. I think), then I want to log. But perhaps there is a better way. 
...

Rick Hight

unread,
Jan 18, 2016, 11:58:54 PM1/18/16
to vert.x
Part 5.
The Java part works, but I have no idea how to use the JS from node.js. Or rather, I don't know the right way.



Step 5 Using Vertx Service Proxies


When you compose a vert.x application, you may want to divvy your applications into services. Using the event bus to do this, forces us to write tons of boilerplate code. In Vertx 3, you can instead use service proxies, which allow you to create code that looks more like a service and less like tons of callback registry with the event bus. This is the main reason they created Vertx service proxies so that you could easily create services that were invokable via the event bus that can be exposed to other JVM languages, browsers or node.js. You can read more about that in the vert.x docs on service proxies.

With Verx Service proxies, you define a Java interface containing methods following the async pattern using Vertx Handler interface, and the common types which are listed on the home page of our wiki and in the vertx docs.

Vertx will use the event bus to invoke our service and get the response back. Vertx will also code generate clients in whatever language you want. All you have to do is include the corresponding artifact vertx-lang, and Vertx will generated the client stubs for that language.

Supported languages:

  • vertx-lang-ceylon Ceylon (supported by vertx team)
  • vertx-lang-ruby Ruby (supported by vertx team)
  • vertx-lang-js JavaScript (supported by vertx team)
  • vertx-lang-groovy Groovy (supported by vertx team)
  • vertx-lang-java Java (supported by vertx team)
  • vertx-lang-jruby JRuby (supported by vertx team)
  • Kotlin support
  • More languages are listed Vertx awesome

Adding service proxy

Add new dependencies to build.gradle

dependencies {
    compile "io.vertx:vertx-core:3.2.0"
    compile 'ch.qos.logback:logback-core:1.1.3'
    compile 'ch.qos.logback:logback-classic:1.1.3'
    compile 'org.slf4j:slf4j-api:1.7.12'
    compile 'org.jetbrains.kotlin:kotlin-stdlib:1.0.0-beta-4584'
    compile 'io.vertx:vertx-tcp-eventbus-bridge:3.2.0'
    compile 'io.vertx:vertx-web:3.2.0'
    compile 'io.vertx:vertx-service-proxy:3.2.0' //NEW Vertx Proxy code
    compile 'io.vertx:vertx-lang-js:3.2.0'       //NEW FOR JS code gen
    compile 'io.vertx:vertx-codegen:3.2.0'       //NEW Vertx code gen
    testCompile group: 'junit', name: 'junit', version: '4.11'
}

Next we need to add the vertx proxy generation support to gradle.

Add new sourceset for generated code to build.gradle

sourceSets {
    generated{
        java.srcDir "${projectDir}/src/generated/java"
    }
}

Create new gradle task to generate Java proxy code to build.gradle

task generateProxies(type: JavaCompile, group: 'build', 
             description: 'Generates the Vertx proxies') {
    source = sourceSets.main.java // input source set
    classpath = configurations.compile //+ configurations.vertx // add processor module to classpath
    // specify javac arguments
    options.compilerArgs = [
            "-proc:only",
            "-processor", "io.vertx.codegen.CodeGenProcessor", // vertx processor here
            "-AoutputDirectory=${projectDir}/src/main"
    ]
    // specify output of generated code
    destinationDir = file("${projectDir}/src/generated/java")
}

Update the compileJava built-in task to call generateProxies task in build.gradle

compileJava{
    dependsOn(generateProxies)
    source    += sourceSets.generated.java
    // specify javac arguments
    options.compilerArgs = [
            "-Acodetrans.output=${projectDir}/src/main"
    ]
}

In the compileJava, we added compilerArgs which will kick off the JavaScript code generation. The generateProxies creates the Java source files that we need for the service proxy, and the compileJava task generates the JavaScript that we need for the browser andnode.js. (I think.)

HelloWorldServiceInterface.java The interface to our service.

package com.github.vertx.node.example;


import io.vertx.codegen.annotations.ProxyClose;
import io.vertx.codegen.annotations.ProxyGen;
import io.vertx.codegen.annotations.VertxGen;

import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.serviceproxy.ProxyHelper;

@ProxyGen
@VertxGen
public interface HelloWorldServiceInterface {



    /**
     * Create a HelloWorldServiceInterface implementation.
     * @param vertx vertx
     * @return HelloWorldServiceInterface
     */
    static HelloWorldServiceInterface create(final Vertx vertx) {
        return new HelloWorldServiceImpl();
    }


    static HelloWorldServiceInterface createProxy(final Vertx vertx,
                                                  final String address) {
        return ProxyHelper.createProxy(HelloWorldServiceInterface.class, vertx, address);

    }

    // Actual service operations here...
    void hello(final String message,
               final Handler<AsyncResult<String>> resultHandler);


    @ProxyClose
    void close();
}

Notice the @ProxyGen and @VertxGen, these are used to generated client code and server binding code for this interface. The @ProxyClose is used to denote that a call to this method will close the remote connection to the event bus.

For the code generation to work you need a package-info.java as follows:

package-info.java

@ModuleGen(groupPackage = "com.github.vertx.node.example", name = "hello-module")
package com.github.vertx.node.example;
import io.vertx.codegen.annotations.ModuleGen;

The @ModuleGen annotation specifies group package and the name of the artifact.

Notice that we have to specify the module name and the group.

The implementation of the HelloWorldService is as follows:

HelloWorldServiceInterface

package com.github.vertx.node.example;

import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;


public class HelloWorldServiceImpl implements HelloWorldServiceInterface {

    @Override
    public void hello(final String message, final Handler<AsyncResult<String>> resultHandler) {

        resultHandler.handle(Future.succeededFuture("Hello World! " + message));

    }

    @Override
    public void close() {

    }
}

At this point the service is really simple. Refer to the vert.x docs on service proxies to see all of the supported types.

HelloWorldServiceImpl.java implementation of our service

package com.github.vertx.node.example;

import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;


public class HelloWorldServiceImpl implements HelloWorldServiceInterface {

    @Override
    public void hello(final String message, final Handler<AsyncResult<String>> resultHandler) {

        resultHandler.handle(Future.succeededFuture("Hello World! " + message));

    }

    @Override
    public void close() {

    }
}

The class HelloWorldServiceImpl does not have a lot of boiler plate code that you would usually expect from using the event bus. The boiler plate code is still there but it is in the generated classes, which we will show at the end.

Register and use the HellowWorldService

Next we want to register and use the HelloWorldService as follows.

Define an address - WebVerticle.java

...
public class WebVerticle extends AbstractVerticle
 {

    public static final String HELLO_WORLD_SERVICE = "hello.world";
    private HelloWorldServiceInterface helloWorldServiceInterface;

Create and register the implementation - WebVerticle.java

...
public class WebVerticle extends AbstractVerticle
 {
    ...

    @Override
    public void start() {

        /* Create the hello world service. */
        final HelloWorldServiceImpl helloWorldService = new HelloWorldServiceImpl();

        /* Register the proxy implementation. */
        ProxyHelper.registerService(HelloWorldServiceInterface.class, vertx,
                helloWorldService, HELLO_WORLD_SERVICE);

The call to ProxyHelper.registerService register this service implementation under the address HELLO_WORLD_SERVICE, i.e., "hello.world".

Create the proxy interface - WebVerticle.java

...
public class WebVerticle extends AbstractVerticle
 {
    ...

    @Override
    public void start() {
        ...
        /* Create the proxy interface to HelloWorldService. */
        helloWorldServiceInterface = ProxyHelper.createProxy(HelloWorldServiceInterface.class, vertx,
                HELLO_WORLD_SERVICE);

The above creates a proxy to the service. When you call methods on this proxy, it will put messages on the event bus that correspond to method calls on the HelloWorldService.

Register the new handler - WebVerticle.java

...
public class WebVerticle extends AbstractVerticle
 {
   ...

    @Override
    public void start() {
        ...

        /* Register a new handler that uses the proxy. */
        router.route("/hello-world/*").handler(event -> 
                 handleCallToHelloWorldProxy(event.request()));

Open up the event bus to the event bus bridge using the address - WebVerticle.java

...
public class WebVerticle extends AbstractVerticle
 {
    ...

    @Override
    public void start() {
        ...

        /* Allow Hello World service to be exposed to Node.js.
         * Also add Add the new Hello World proxy. */
        final BridgeOptions options = new BridgeOptions
()
                ...
                .addInboundPermitted(new PermittedOptions().setAddress(HELLO_WORLD_SERVICE))
                .addOutboundPermitted(new PermittedOptions().setAddress(HELLO_WORLD_SERVICE));

The above allows inbound and outbound communication with the HelloWorldService although at this point, we are just implementing inbound.

Call the service proxy interface from the new handler - WebVerticle.java

...
public class WebVerticle extends AbstractVerticle
 {
    ...

    /**
     * Handle call to hello world service proxy from REST
     * @param httpRequest httpRequest
     */
    private void handleCallToHelloWorldProxy(final HttpServerRequest httpRequest) {

        /** Call the service proxy interface for Hello World. */
        helloWorldServiceInterface.hello(httpRequest.getParam("msg"), response -> {
            if (response.succeeded()) {
                logger.debug("Successfully invoked HelloWorldService Proxy to service {}", 
                             response.result());
                httpRequest.response().end(response.result());
            } else {

                logger.error("Can't send message to hello world service", 
                             response.cause());
                
//noinspection ThrowableResultOfMethodCallIgnored
                httpRequest.response().setStatusCode(500).end(
                                             response.cause().getMessage());
            }
        });
    }

Here is the whole class.

WebVerticle.java

package com.github.vertx.node.example;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.sockjs.BridgeOptions;
import io.vertx.ext.web.handler.sockjs.PermittedOptions;
import io.vertx.ext.web.handler.sockjs.SockJSHandler;
import io.vertx.serviceproxy.ProxyHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This gets started by the MainVerticle.
 * It configures the event bus bridge and install the REST routes.
 */
public class WebVerticle extends AbstractVerticle
 {

    public static final String HELLO_WORLD_SERVICE = "hello.world";
    
private final Logger logger = LoggerFactory.getLogger(WebVerticle.
class);


    private HelloWorldServiceInterface helloWorldServiceInterface;

    @Override
    public void start() {


        /* Create the hello world service. */
        final HelloWorldServiceImpl helloWorldService = new HelloWorldServiceImpl();

        /* Register the proxy implementation. */
        ProxyHelper.registerService(HelloWorldServiceInterface.class, vertx,
                helloWorldService, HELLO_WORLD_SERVICE);

        
/* Create vertx web router. */
        final Router router = Router.
router(vertx);

        /* Create the proxy interface to HelloWorldService. */
        helloWorldServiceInterface = ProxyHelper.createProxy(HelloWorldServiceInterface.class, vertx,
                HELLO_WORLD_SERVICE);


        
/* Install our original "REST" handler at the /hello/ uri. */
        router.route("/hello/*").handler(event -> handleHttpRequestToHelloWorld(event.
request()));

        /* Register a new handler that uses the proxy. */
        router.route("/hello-world/*").handler(event -> handleCallToHelloWorldProxy(event.request()));



        
/* Allow Hello World service to be exposed to Node.js.
         * Also add Add the new Hello World proxy. */
        final BridgeOptions options = new BridgeOptions()
                .addInboundPermitted(
                        new PermittedOptions().setAddress(Services.HELLO_WORLD.
toString()))
                .addInboundPermitted(new PermittedOptions().setAddress(HELLO_WORLD_SERVICE))
                .addOutboundPermitted(new PermittedOptions().setAddress(HELLO_WORLD_SERVICE));

        
/* Configure bridge at this HTTP/WebSocket URI. */
        router.route("/eventbus/*").handler(SockJSHandler.create(vertx).bridge(options));

        /* Install router into vertx. */
        vertx.createHttpServer()
                .requestHandler(router::accept)
                .listen(8080);
    }

    /**
     * Handle call to hello world service proxy from REST
     * @param httpRequest httpRequest
     */
    private void handleCallToHelloWorldProxy(final HttpServerRequest httpRequest) {

        /** Call the service proxy interface for Hello World. */
        helloWorldServiceInterface.hello(httpRequest.getParam("msg"), response -> {
            if (response.succeeded()) {
                logger.debug("Successfully invoked HelloWorldService Proxy to service {}", response.result());
                httpRequest.response().end(response.result());
            } else {

                logger.error("Can't send message to hello world service", response.cause());
                

Code generation for Java

The generateProxies task that we defined gets used to generate the Java client side and Java server side bindings for our service. The code gets put into ${projectDir}/src/generated/javawhich we also add to our classpath so that it gets included with our fat jar.

HelloWorldServiceInterfaceVertxEBProxy.java - GENERATED CODE

/*
* Copyright 2014 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package com.github.vertx.node.example;

import com.github.vertx.node.example.HelloWorldServiceInterface;
import io.vertx.core.eventbus.DeliveryOptions;
import io.vertx.core.Vertx;
import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import io.vertx.core.json.JsonArray;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.function.Function;
import io.vertx.serviceproxy.ProxyHelper;
import io.vertx.core.Vertx;
import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import com.github.vertx.node.example.HelloWorldServiceInterface;

/*
  Generated Proxy code - DO NOT EDIT
  @author Roger the Robot
*/
public class HelloWorldServiceInterfaceVertxEBProxy implements HelloWorldServiceInterface {

  private Vertx _vertx;
  private String _address;
  private DeliveryOptions _options;
  private boolean closed;

  public HelloWorldServiceInterfaceVertxEBProxy(Vertx vertx, String address) {
    this(vertx, address, null);
  }

  public HelloWorldServiceInterfaceVertxEBProxy(Vertx vertx, String address, DeliveryOptions options) {
    this._vertx = vertx;
    this._address = address;
    this._options = options;
  }

  public void hello(String message, Handler<AsyncResult<String>> resultHandler) {
    if (closed) {
      resultHandler.handle(Future.failedFuture(new IllegalStateException("Proxy is closed")));
      return;
    }
    JsonObject _json = new JsonObject();
    _json.put("message", message);
    DeliveryOptions _deliveryOptions = (_options != null) ? new DeliveryOptions(_options) : new DeliveryOptions();
    _deliveryOptions.addHeader("action", "hello");
    _vertx.eventBus().<String>send(_address, _json, _deliveryOptions, res -> {
      if (res.failed()) {
        resultHandler.handle(Future.failedFuture(res.cause()));
      } else {
        resultHandler.handle(Future.succeededFuture(res.result().body()));
      }
    });
  }

  public void close() {
    if (closed) {
      throw new IllegalStateException("Proxy is closed");
    }
    closed = true;
    JsonObject _json = new JsonObject();
    DeliveryOptions _deliveryOptions = (_options != null) ? new DeliveryOptions(_options) : new DeliveryOptions();
    _deliveryOptions.addHeader("action", "close");
    _vertx.eventBus().send(_address, _json, _deliveryOptions);
  }


  private List<Character> convertToListChar(JsonArray arr) {
    List<Character> list = new ArrayList<>();
    for (Object obj: arr) {
      Integer jobj = (Integer)obj;
      list.add((char)(int)jobj);
    }
    return list;
  }

  private Set<Character> convertToSetChar(JsonArray arr) {
    Set<Character> set = new HashSet<>();
    for (Object obj: arr) {
      Integer jobj = (Integer)obj;
      set.add((char)(int)jobj);
    }
    return set;
  }

  private <T> Map<String, T> convertMap(Map map) {
    if (map.isEmpty()) { 
      return (Map<String, T>) map; 
    } 

    Object elem = map.values().stream().findFirst().get(); 
    if (!(elem instanceof Map) && !(elem instanceof List)) { 
      return (Map<String, T>) map; 
    } else { 
      Function<Object, T> converter; 
      if (elem instanceof List) { 
        converter = object -> (T) new JsonArray((List) object); 
      } else { 
        converter = object -> (T) new JsonObject((Map) object); 
      } 
      return ((Map<String, T>) map).entrySet() 
       .stream() 
       .collect(Collectors.toMap(Map.Entry::getKey, converter::apply)); 
    } 
  }
  private <T> List<T> convertList(List list) {
    if (list.isEmpty()) { 
          return (List<T>) list; 
        } 

    Object elem = list.get(0); 
    if (!(elem instanceof Map) && !(elem instanceof List)) { 
      return (List<T>) list; 
    } else { 
      Function<Object, T> converter; 
      if (elem instanceof List) { 
        converter = object -> (T) new JsonArray((List) object); 
      } else { 
        converter = object -> (T) new JsonObject((Map) object); 
      } 
      return (List<T>) list.stream().map(converter).collect(Collectors.toList()); 
    } 
  }
  private <T> Set<T> convertSet(List list) {
    return new HashSet<T>(convertList(list));
  }
}

HelloWorldServiceInterfaceVertxProxyHandler.java - GENERATED CODE

/*
* Copyright 2014 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package com.github.vertx.node.example;

import com.github.vertx.node.example.HelloWorldServiceInterface;
import io.vertx.core.Vertx;
import io.vertx.core.Handler;
import io.vertx.core.AsyncResult;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.eventbus.Message;
import io.vertx.core.eventbus.MessageConsumer;
import io.vertx.core.eventbus.DeliveryOptions;
import io.vertx.core.eventbus.ReplyException;
import io.vertx.core.json.JsonObject;
import io.vertx.core.json.JsonArray;
import java.util.Collection;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import io.vertx.serviceproxy.ProxyHelper;
import io.vertx.serviceproxy.ProxyHandler;
import io.vertx.core.Vertx;
import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import com.github.vertx.node.example.HelloWorldServiceInterface;

/*
  Generated Proxy code - DO NOT EDIT
  @author Roger the Robot
*/
public class HelloWorldServiceInterfaceVertxProxyHandler extends ProxyHandler {

  public static final long DEFAULT_CONNECTION_TIMEOUT = 5 * 60; // 5 minutes 

  private final Vertx vertx;
  private final HelloWorldServiceInterface service;
  private final long timerID;
  private long lastAccessed;
  private final long timeoutSeconds;

  public HelloWorldServiceInterfaceVertxProxyHandler(Vertx vertx, HelloWorldServiceInterface service) {
    this(vertx, service, DEFAULT_CONNECTION_TIMEOUT);
  }

  public HelloWorldServiceInterfaceVertxProxyHandler(Vertx vertx, HelloWorldServiceInterface service, long timeoutInSecond) {
    this(vertx, service, true, timeoutInSecond);
  }

  public HelloWorldServiceInterfaceVertxProxyHandler(Vertx vertx, HelloWorldServiceInterface service, boolean topLevel, long timeoutSeconds) {
    this.vertx = vertx;
    this.service = service;
    this.timeoutSeconds = timeoutSeconds;
    if (timeoutSeconds != -1 && !topLevel) {
      long period = timeoutSeconds * 1000 / 2;
      if (period > 10000) {
        period = 10000;
      }
      this.timerID = vertx.setPeriodic(period, this::checkTimedOut);
    } else {
      this.timerID = -1;
    }
    accessed();
  }

  public MessageConsumer<JsonObject> registerHandler(String address) {
    MessageConsumer<JsonObject> consumer = vertx.eventBus().<JsonObject>consumer(address).handler(this);
    this.setConsumer(consumer);
    return consumer;
  }

  private void checkTimedOut(long id) {
    long now = System.nanoTime();
    if (now - lastAccessed > timeoutSeconds * 1000000000) {
      service.close();
      close();
    }
  }

  @Override
  public void close() {
    if (timerID != -1) {
      vertx.cancelTimer(timerID);
    }
    super.close();
  }

  private void accessed() {
    this.lastAccessed = System.nanoTime();
  }

  public void handle(Message<JsonObject> msg) {
    try {
      JsonObject json = msg.body();
      String action = msg.headers().get("action");
      if (action == null) {
        throw new IllegalStateException("action not specified");
      }
      accessed();
      switch (action) {


        case "hello": {
          service.hello((java.lang.String)json.getValue("message"), createHandler(msg));
          break;
        }
        case "close": {
          service.close();
          close();
          break;
        }
        default: {
          throw new IllegalStateException("Invalid action: " + action);
        }
      }
    } catch (Throwable t) {
      msg.fail(-1, t.getMessage());
      throw t;
    }
  }

  private <T> Handler<AsyncResult<T>> createHandler(Message msg) {
    return res -> {
      if (res.failed()) {
        msg.fail(-1, res.cause().getMessage());
      } else {
        if (res.result() != null  && res.result().getClass().isEnum()) {          msg.reply(((Enum) res.result()).name());        } else {          msg.reply(res.result());        }      }
    };
  }

  private <T> Handler<AsyncResult<List<T>>> createListHandler(Message msg) {
    return res -> {
      if (res.failed()) {
        msg.fail(-1, res.cause().getMessage());
      } else {
        msg.reply(new JsonArray(res.result()));
      }
    };
  }

  private <T> Handler<AsyncResult<Set<T>>> createSetHandler(Message msg) {
    return res -> {
      if (res.failed()) {
        msg.fail(-1, res.cause().getMessage());
      } else {
        msg.reply(new JsonArray(new ArrayList<>(res.result())));
      }
    };
  }

  private Handler<AsyncResult<List<Character>>> createListCharHandler(Message msg) {
    return res -> {
      if (res.failed()) {
        msg.fail(-1, res.cause().getMessage());
      } else {
        JsonArray arr = new JsonArray();
        for (Character chr: res.result()) {
          arr.add((int) chr);
        }
        msg.reply(arr);
      }
    };
  }

  private Handler<AsyncResult<Set<Character>>> createSetCharHandler(Message msg) {
    return res -> {
      if (res.failed()) {
        msg.fail(-1, res.cause().getMessage());
      } else {
        JsonArray arr = new JsonArray();
        for (Character chr: res.result()) {
          arr.add((int) chr);
        }
        msg.reply(arr);
      }
    };
  }

  private <T> Map<String, T> convertMap(Map map) {
    return (Map<String, T>)map;
  }

  private <T> List<T> convertList(List list) {
    return (List<T>)list;
  }

  private <T> Set<T> convertSet(List list) {
    return new HashSet<T>((List<T>)list);
  }
}

Code generation for JavaScript

The compileJava generates the JavaScript for this example when we specify "-Acodetrans.output=${projectDir}/src/main".

hello_world_service_interface.js - GENERATED CODE

/*
 * Copyright 2014 Red Hat, Inc.
 *
 * Red Hat licenses this file to you under the Apache License, version 2.0
 * (the "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

/** @module hello-module-js/hello_world_service_interface */
var utils = require('vertx-js/util/utils');
var Vertx = require('vertx-js/vertx');

var io = Packages.io;
var JsonObject = io.vertx.core.json.JsonObject;
var JHelloWorldServiceInterface = com.github.vertx.node.example.HelloWorldServiceInterface;

/**
 @class
*/
var HelloWorldServiceInterface = function(j_val) {

  var j_helloWorldServiceInterface = j_val;
  var that = this;

  /**

   @public
   @param message {string} 
   @param resultHandler {function} 
   */
  this.hello = function(message, resultHandler) {
    var __args = arguments;
    if (__args.length === 2 && typeof __args[0] === 'string' && typeof __args[1] === 'function') {
      j_helloWorldServiceInterface["hello(java.lang.String,io.vertx.core.Handler)"](message, function(ar) {
      if (ar.succeeded()) {
        resultHandler(ar.result(), null);
      } else {
        resultHandler(null, ar.cause());
      }
    });
    } else throw new TypeError('function invoked with invalid arguments');
  };

  /**

   @public

   */
  this.close = function() {
    var __args = arguments;
    if (__args.length === 0) {
      j_helloWorldServiceInterface["close()"]();
    } else throw new TypeError('function invoked with invalid arguments');
  };

  // A reference to the underlying Java delegate
  // NOTE! This is an internal API and must not be used in user code.
  // If you rely on this property your code is likely to break if we change it / remove it without warning.
  this._jdel = j_helloWorldServiceInterface;
};

/**
 Create a HelloWorldServiceInterface implementation.

 @memberof module:hello-module-js/hello_world_service_interface
 @param vertx {Vertx} vertx 
 @return {HelloWorldServiceInterface} HelloWorldServiceInterface
 */
HelloWorldServiceInterface.create = function(vertx) {
  var __args = arguments;
  if (__args.length === 1 && typeof __args[0] === 'object' && __args[0]._jdel) {
    return utils.convReturnVertxGen(JHelloWorldServiceInterface["create(io.vertx.core.Vertx)"](vertx._jdel), HelloWorldServiceInterface);
  } else throw new TypeError('function invoked with invalid arguments');
};

/**

 @memberof module:hello-module-js/hello_world_service_interface
 @param vertx {Vertx} 
 @param address {string} 
 @return {HelloWorldServiceInterface}
 */
HelloWorldServiceInterface.createProxy = function(vertx, address) {
  var __args = arguments;
  if (__args.length === 2 && typeof __args[0] === 'object' && __args[0]._jdel && typeof __args[1] === 'string') {
    return utils.convReturnVertxGen(JHelloWorldServiceInterface["createProxy(io.vertx.core.Vertx,java.lang.String)"](vertx._jdel, address), HelloWorldServiceInterface);
  } else throw new TypeError('function invoked with invalid arguments');
};

// We export the Constructor function
module.exports = HelloWorldServiceInterface;

hello_world_service_interface.js - GENERATED CODE

/*
 * Copyright 2014 Red Hat, Inc.
 *
 * Red Hat licenses this file to you under the Apache License, version 2.0
 * (the "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

/** @module hello-module-js/hello_world_service_interface */
!function (factory) {
  if (typeof require === 'function' && typeof module !== 'undefined') {
    factory();
  } else if (typeof define === 'function' && define.amd) {
    // AMD loader
    define('hello-module-js/hello_world_service_interface-proxy', [], factory);
  } else {
    // plain old include
    HelloWorldServiceInterface = factory();
  }
}(function () {

  /**
 @class
  */
  var HelloWorldServiceInterface = function(eb, address) {

    var j_eb = eb;
    var j_address = address;
    var closed = false;
    var that = this;
    var convCharCollection = function(coll) {
      var ret = [];
      for (var i = 0;i < coll.length;i++) {
        ret.push(String.fromCharCode(coll[i]));
      }
      return ret;
    };

    /**

     @public
     @param message {string} 
     @param resultHandler {function} 
     */
    this.hello = function(message, resultHandler) {
      var __args = arguments;
      if (__args.length === 2 && typeof __args[0] === 'string' && typeof __args[1] === 'function') {
        if (closed) {
          throw new Error('Proxy is closed');
        }
        j_eb.send(j_address, {"message":__args[0]}, {"action":"hello"}, function(err, result) { __args[1](err, result &&result.body); });
        return;
      } else throw new TypeError('function invoked with invalid arguments');
    };

    /**

     @public

     */
    this.close = function() {
      var __args = arguments;
      if (__args.length === 0) {
        if (closed) {
          throw new Error('Proxy is closed');
        }
        j_eb.send(j_address, {}, {"action":"close"});
        closed = true;
        return;
      } else throw new TypeError('function invoked with invalid arguments');
    };

  };

  /**
   Create a HelloWorldServiceInterface implementation.

   @memberof module:hello-module-js/hello_world_service_interface
   @param vertx {Vertx} vertx 
   @return {HelloWorldServiceInterface} HelloWorldServiceInterface
   */
  HelloWorldServiceInterface.create = function(vertx) {
    var __args = arguments;
    if (__args.length === 1 && typeof __args[0] === 'object' && __args[0]._jdel) {
      if (closed) {
        throw new Error('Proxy is closed');
      }
      j_eb.send(j_address, {"vertx":__args[0]}, {"action":"create"});
      return;
    } else throw new TypeError('function invoked with invalid arguments');
  };

  /**

   @memberof module:hello-module-js/hello_world_service_interface
   @param vertx {Vertx} 
   @param address {string} 
   @return {HelloWorldServiceInterface}
   */
  HelloWorldServiceInterface.createProxy = function(vertx, address) {
    var __args = arguments;
    if (__args.length === 2 && typeof __args[0] === 'object' && __args[0]._jdel && typeof __args[1] === 'string') {
      if (closed) {
        throw new Error('Proxy is closed');
      }
      j_eb.send(j_address, {"vertx":__args[0], "address":__args[1]}, {"action":"createProxy"});
      return;
    } else throw new TypeError('function invoked with invalid arguments');
  };

  if (typeof exports !== 'undefined') {
    if (typeof module !== 'undefined' && module.exports) {
      exports = module.exports = HelloWorldServiceInterface;
    } else {
      exports.HelloWorldServiceInterface = HelloWorldServiceInterface;
    }
  } else {
    return HelloWorldServiceInterface;
  }
});

Running this

To get the Java service proxy to run, run the application.

Run MainVerticle

$ gradle clean shadowJar
$ find . -name "*.jar"
$ java -jar ./build/libs/vertx-1.0-SNAPSHOT-fat.jar

Now hit the REST end point that calls the HelloWorld service proxy through the HTTP end point.

Hit the REST end point which calls the HelloWorldService proxy

$ curl http://localhost:8080/hello-world/foo?msg=Rick
Hello World! Rick
...

Arnaud Estève

unread,
Jan 19, 2016, 3:59:27 AM1/19/16
to vert.x
I would have written it this way : https://gist.github.com/aesteve/4f828938a955af0a931f

To avoid timer checking.
Reply all
Reply to author
Forward
0 new messages