© 2022 The original authors.

1. Overview

This is the Vertx extension for WildFly Application Server.

Eclipse Vert.x is a toolkit to build reactive applications on the JVM. Integrating it adds more reactive power to WildFly.

It allows you to define a Vert.x instance using WildFly’s management model. The Vert.x instance is managed by the WildFly server. It can be accessed using JNDI lookup or CDI injection in your enterprise or web applications.

You can access the Vert.x core APIs and some component APIs in your applications.

You can package the Vert.x verticle implementation in the application, and deploy it to a WildFly server with a deployment descriptor. The server will do the deployment accordingly.

In case of clustering Vert.x instance, this extension uses vertx-infinispan as the cluster manager to be able to talk with remote Vert.x instances. You can specify the necessary JGroups settings for Vert.x, either using the standard WildFly management model, or specifying a JGroups stack file.

The generated distribution of this extension is a full WildFly server with above capabilities. The build produces only standalone configurations. Domain mode is not supported.

It also produces a Galleon feature pack, which can be installed to an existing WildFly server. You can install either the full feature pack, or layers of the feature pack.

The main goal of this extension is to explore a way for JakartaEE developers to benefit from some reactive utilities.

2. Build the extension

This extension uses Maven as the build tool.

Requirements to build the extension are:

  • JDK 11+

  • Maven 3.2.5+

To build the extension, simply run the following command:

Build command
mvn clean install

When the command finishes, it will have produced two server distributions and a Galleon feature pack:

  • Thin server

    • Located at build/target/wildfly-vertx-build-{version}/.

    • The thin server contains all configurations and JBoss module files (module.xml) which define the Maven artifact, without the jar files.

  • Full server

    • Located at dist/target/wildfly-vertx-dist-{version}/.

    • The full server contains everything including jar files.

  • Galleon feature pack

    • Located at galleon-feature-pack/wildfly-feature-pack/target/wildfly-vertx-feature-pack-{version}.zip.

    • This feature pack can be installed in an existing WildFly server.

Both produced servers have 2 standalone configurations:

  • standalone.xml

    • This is the setup with the Vert.x subsystem added to the default standalone.xml in WildFly server.

  • standalone-ha.xml

    • This is the setup with the Vert.x subsystem added to the default standalone-ha.xml in WildFly server.

    • The default Vert.x in this setup is a clustered Vert.x with default configuration.

2.1. Start the server

You can start the server like you do for normal WildFly server by specifying the desired configuration file:

Start Server
$SERVER_HOME/bin/standalone.sh -c standalone-ha.xml

3. How to install to an existing WildFly server

If you have an existing WildFly server already, you have 2 options to have this extension installed, one is to install the whole feature pack, the other is to install part of the feature pack.

3.1. Install the whole feature pack

Install feature pack to existing server
# This line installs a WildFly server to local_server directory:
galleon.sh install --dir=local_server "wildfly-ee@maven(org.jboss.universe:community-universe):current#${version.wildfly}"

# This line installs the wildfly-vertx-feature-pack on top of local WildFly server:
galleon.sh install --dir=local_server org.wildfly.extension.vertx:wildfly-vertx-feature-pack:1.0.0-SNAPSHOT

after it completes, the vertx subsystem and the required bits are downloaded and installed to the existing server.

3.2. Install the layers of the feature pack

The feature pack has 2 Galleon layers produced:

  • vertx

  • vertx-ha

You can specify one of them to install to an existing server, similar as above, run:

Install part of the feature pack to existing server
# This line installs a WildFly server to local_server directory:
galleon.sh install --dir=local_server "wildfly-ee@maven(org.jboss.universe:community-universe):current#${version.wildfly}"

# This line installs the vertx layer only from wildfly-vertx-feature-pack.
galleon.sh install --dir=local_server org.wildfly.extension.vertx:wildfly-vertx-feature-pack:1.0.0-SNAPSHOT --layers=vertx

this will update the default configuration standalone.xml and it will download necessary bits.

in case of clustered Vertx, you need layer of vertx-ha, and you need to specify the ha configuration to update:

# This line installs a WildFly server to local_server directory:
galleon.sh install --dir=local_server "wildfly-ee@maven(org.jboss.universe:community-universe):current#${version.wildfly}"

# This line installs the vertx-ha layer only from wildfly-vertx-feature-pack with config to standalone-ha.xml.
galleon.sh install --dir=local_server org.wildfly.extension.vertx:wildfly-vertx-feature-pack:1.0.0-SNAPSHOT --layers=vertx-ha --config=standalone-ha.xml
To have galleon.sh script work, please check the Galleon Provisioning Guide on how to download and install the Gallon CLI tool.

4. The vertx subsystem configuration

There is a Vert.x instance created in each standalone configuration by default.

After server starts, in a JBoss CLI console, you can see the Vert.x resource like:

Read Vertx Resource
[standalone@localhost:9990 /] /subsystem=vertx/service=vertx:read-resource()
{
    "outcome" => "success",
    "result" => {
        "clustered" => false,
        "forked-channel" => false,
        "jgroups-channel" => undefined,
        "jgroups-stack-file" => undefined,
        "option-name" => undefined
    }
}

The Vert.x instance has the following attributes:

  • clustered - Flag that if it is a clustered Vert.x instance.

    • Defaults to false

    • When it is set to true, either jgroups-channel or jgroups-stack-file should be specified, otherwise, the default InfinispanClusterManager will be used.

  • forked-channel - Flag if the forked channel needs to be used

    • It is used only when clustered is true and jgroups-channel is specified.

    • Technically, when it is set to true, the JGroupsRequirement.CHANNEL_FACTORY from WildFly clustering JGroups SPI is used for the JChannel creation, while it is set to false(default), the JGroupsRequirement.CHANNEL_SOURCE is used for the JChannel creation.

  • jgroups-channel - The JGroups Channel which is used for JChannel creation

    • It is used only when clustered is true.

    • It comes from resources under /subsystem=jgroups/channel=XX.

  • jgroups-stack-file - The JGroups stack file used to compose the JGroups cluster

    • It is used only when clustered is true.

    • It is either an absolute file starts with / or a file relatives to ${jboss.server.config.dir}

  • option-name - The option name used to create the Vert.x instance

    • The default VertxOptions instance is used to create the Vert.x instance if this is not set.

    • It comes from resources either in /subsystem=vertx/vertx-option=XX or in /subsystem=vertx/vertx-option-file=XX.

when both jgroups-channel and jgroups-stack-file are specified, there will be an exception thrown out.

4.1. Create an option for Vert.x

You have 2 options to create a VertxOptions instance, one is to create the option by specifying a JSON file path, the other is to create the option by specifying each attribute using WildFly management model.

Create VertxOptions using a JSON file

Using the following command in JBoss CLI to create a VertxOptions by specifying a JSON file path:

Create VertxOptions by a JSON file
[standalone@localhost:9990 /] /subsystem=vertx/vertx-option-file=vof:add(path=default-vertx-options.json)
{"outcome" => "success"}

Here the path of default-vertx-options.json is expected at ${jboss.server.config.dir}. After it completes, you can refer to vof in the command:

Update Vertx to use a specific option name
[standalone@localhost:9990 /] /subsystem=vertx/service=vertx:write-attribute(name=option-name,value=vof)
{
    "outcome" => "success",
    "response-headers" => {
        "operation-requires-reload" => true,
        "process-state" => "reload-required"
    }
}
You need to run :reload after updating any attribute of the Vert.x instance.
The format of the JSON file follows what VertxOptions(JsonObject json) requires.
Create an option with attributes

You can create a VertxOptions by specifying each attribute as well.

Let’s take a look at what an option resource normally has:

Typical VertxOptions resource
[standalone@localhost:9990 /] /subsystem=vertx/vertx-option=vo:read-resource()
{
    "outcome" => "success",
    "result" => {
        "address-resolver-option" => "aro",
        "blocked-thread-check-interval" => undefined,
        "blocked-thread-check-interval-unit" => undefined,
        "classpath-resolving-enabled" => undefined,
        "disable-tccl" => undefined,
        "event-loop-pool-size" => undefined,
        "eventbus-option" => "ebo",
        "file-cache-enabled" => undefined,
        "ha-enabled" => undefined,
        "ha-group" => undefined,
        "internal-blocking-pool-size" => undefined,
        "max-eventloop-execute-time" => undefined,
        "max-eventloop-execute-time-unit" => undefined,
        "max-worker-execute-time" => undefined,
        "max-worker-execute-time-unit" => undefined,
        "prefer-native-transport" => undefined,
        "quorum-size" => undefined,
        "warning-exception-time" => undefined,
        "warning-exception-time-unit" => undefined,
        "worker-pool-size" => undefined
    }
}

All attributes except for address-resolver-option and eventbus-option are primitive types.

  • The address-resolver-option refers to what /subsystem=vertx/address-resolver-option=aro defines:

AddressResolverOptions definition:
[standalone@localhost:9990 /] /subsystem=vertx/address-resolver-option=aro:read-resource()
{
    "outcome" => "success",
    "result" => {
        "cache-max-time-to-live" => undefined,
        "cache-min-time-to-live" => undefined,
        "cache-negative-time-to-live" => undefined,
        "hosts-path" => undefined,
        "hosts-value" => undefined,
        "max-queries" => 50,
        "n-dots" => undefined,
        "opt-resource-enabled" => undefined,
        "query-time-out" => undefined,
        "rd-flag" => undefined,
        "rotate-servers" => undefined,
        "round-robin-inet-address" => undefined,
        "search-domains" => undefined,
        "servers" => undefined
    }
}
  • The eventbus-option refers to what /subsystem=vertx/eventbus-option=ebo defines:

EventBusOptions definition:
[standalone@localhost:9990 /] /subsystem=vertx/eventbus-option=ebo:read-resource()
{
    "outcome" => "success",
    "result" => {
        "accept-backlog" => undefined,
        "client-auth" => undefined,
        "cluster-node-metadata" => undefined,
        "cluster-ping-interval" => undefined,
        "cluster-ping-reply-interval" => undefined,
        "cluster-public-host" => undefined,
        "cluster-public-port" => undefined,
        "connect-timeout" => undefined,
        "crl-paths" => undefined,
        "crl-values" => undefined,
        "enabled-cipher-suites" => undefined,
        "enabled-secure-transport-protocols" => undefined,
        "host" => undefined,
        "idle-timeout" => undefined,
        "idle-timeout-unit" => undefined,
        "key-cert-option" => undefined,
        "log-activity" => undefined,
        "openssl-session-cache-enabled" => undefined,
        "port" => undefined,
        "read-idle-timeout" => undefined,
        "receive-buffer-size" => undefined,
        "reconnect-attempts" => undefined,
        "reconnect-interval" => undefined,
        "reuse-address" => undefined,
        "reuse-port" => undefined,
        "send-buffer-size" => undefined,
        "so-linger" => 200,
        "ssl" => undefined,
        "ssl-engine-type" => undefined,
        "ssl-hand-shake-timeout" => undefined,
        "ssl-hand-shake-timeout-unit" => undefined,
        "tcp-cork" => undefined,
        "tcp-fast-open" => undefined,
        "tcp-keep-alive" => undefined,
        "tcp-no-delay" => undefined,
        "tcp-quick-ack" => undefined,
        "traffic-class" => undefined,
        "trust-all" => undefined,
        "trust-option" => undefined,
        "use-alpn" => undefined,
        "write-idle-timeout" => undefined
    }
}
There are sub options used to create the EventBusOptions, including key-store-option, pem-key-cert-option, pem-trust-option, cluster-node-metadata, please use the corresponding :read-resource-description() operation for each attribute description.
Any update to the options won’t require reload unless the option is referenced by the Vertx instance.

4.2. Clustered Vert.x instance

You need to specify clustered=true to for a clustered Vert.x instance, and you need to start the server with vertx-ha available, basically with standalone-vertx*-ha.xml configurations.

You have 2 options to set up the clustering configuration, one is to specify the JGroups stack file, the other is to specify the jgroups-channel from jgroups subsystem to compose the JGroups cluster.

Using a JGroups stack file

You can update the clustered Vert.x instance by specifying the JGroups stack file with the following command:

Update Vert.x instance to use a JGroups stack file
[standalone@localhost:9990 /] /subsystem=vertx/service=vertx:write-attribute(name=jgroups-stack-file,value=jgroups-stack.xml)
{
    "outcome" => "success",
    "response-headers" => {
        "operation-requires-reload" => true,
        "process-state" => "reload-required"
    }
}

The above command suppose there is a jgroups-stack.xml file existing at ${jboss.server.config.dir}.

The content inside the JGroups stack file should be consistent with the remote Vert.x instance to be able to compose a cluster. Please refer to http://www.jgroups.org/manual4/ on the detail configuration of the stack.
Using a channel from jgroups subsystem

Please use the following commands to create a JGroups channel for the clustered Vert.x instance, which matches what default Vert.x Infinispan cluster ships:

Configure JGroups channel for Vert.x
batch
/socket-binding-group=standard-sockets/socket-binding=jgroups-vertx:add(port=7800)
/socket-binding-group=standard-sockets/socket-binding=jgroups-vertx-mping:add(interface=private, multicast-port=46655, multicast-address=228.6.7.8
/socket-binding-group=standard-sockets/socket-binding=jgroups-vertx-tcp-fd:add(interface=private, port=57800)

/subsystem=jgroups/stack=tcp-vertx:add()
/subsystem=jgroups/stack=tcp-vertx/transport=TCP:add(socket-binding=jgroups-vertx)
/subsystem=jgroups/stack=tcp-vertx/protocol=MPING:add(socket-binding=jgroups-vertx-mping)
/subsystem=jgroups/stack=tcp-vertx/protocol=MERGE3:add()
/subsystem=jgroups/stack=tcp-vertx/protocol=FD_SOCK:add(socket-binding=jgroups-vertx-tcp-fd)
/subsystem=jgroups/stack=tcp-vertx/protocol=FD_ALL:add()
/subsystem=jgroups/stack=tcp-vertx/protocol=VERIFY_SUSPECT:add()
/subsystem=jgroups/stack=tcp-vertx/protocol=pbcast.NAKACK2:add()
/subsystem=jgroups/stack=tcp-vertx/protocol=UNICAST3:add()
/subsystem=jgroups/stack=tcp-vertx/protocol=pbcast.STABLE:add()
/subsystem=jgroups/stack=tcp-vertx/protocol=pbcast.GMS:add()
/subsystem=jgroups/stack=tcp-vertx/protocol=MFC:add()
/subsystem=jgroups/stack=tcp-vertx/protocol=FRAG3:add()
/subsystem=jgroups/channel=vertx:add(stack=tcp-vertx, cluster=ISPN)
run-batch
:reload

Now update the Vert.x instance to use the JGroups channel created above:

Update Vert.x instance with JGroups channel specified
[standalone@localhost:9990 /] /subsystem=vertx/service=vertx:write-attribute(name=jgroups-channel,value=vertx)
{
    "outcome" => "success",
    "response-headers" => {
        "operation-requires-reload" => true,
        "process-state" => "reload-required"
    }
}

[standalone@localhost:9990 /] :reload
{
    "outcome" => "success",
    "result" => undefined
}

Now you have updated the clustered Vert.x instance with JGroups channel set up from jgroups subsystem.

Remember to specify -Djgroups.bind.address=127.0.0.1 in your another Vert.x instance to be able to compose the cluster for local test.

5. Vertx used in application

You can use the Vert.x instance in your applications.

Vert.x instance is the key to all Vert.x applications, each function and capability comes from the Vert.x instance, so having the Vert.x instance injected in the application codes is important.

This extension provides 2 ways to access the Vert.x instance.

5.1. Access the Vert.x instance using JNDI lookup

you can access the Vert.x using the @Resource(lookup = "java:/jboss/vertx/default") annotation like:

Access Vert.x using JNDI lookup
@WebServlet(value = "/async", asyncSupported = true)
public class AsyncServlet extends HttpServlet {

    @Resource(name = "java:/jboss/vertx/default")
    private Vertx vertx;

        @Override
    public void init() throws ServletException {
        vertx.eventBus()
                .<String>consumer("echo")
                .handler(msg -> msg.reply(msg.body()));
    }
}

5.2. Access the Vert.x instance using CDI

The other way to use the Vert.x instance in the application is to use @Inject Vertx vertx annotation. When the CDI is activated(via a beans.xml file bundled in the deployment archive), the Vert.x instance will be injected.

Access Vert.x instance using CDI
@Stateless
public class EchoService {
    @Inject
    private Vertx vertx;

    @Asynchronous
    public Future<String> echo(String message) {
        return (CompletableFuture<String>)vertx.eventBus()
                   .request("echo", message)
                   .map(msg -> msg.body().toString()).toCompletionStage();
    }

}

6. Deploy verticles to WildFly server

With this extension integrated, you can deploy the Vert.x verticle implementation to the WildFly server by specifying a deployment descriptor.

The availability of the deployment descriptor makes it a vertx deployment, which means that users can access Vert.x instance in their applications.

A typical war deployment structure which contains a verticle implementation is:

WAR deployment with verticle implementation
test-verticle.war:
/WEB-INF/
/WEB-INF/vertx.json
/WEB-INF/classes/
/WEB-INF/classes/org/
/WEB-INF/classes/org/wildfly/
/WEB-INF/classes/org/wildfly/extension/
/WEB-INF/classes/org/wildfly/extension/vertx/
/WEB-INF/classes/org/wildfly/extension/vertx/test/
/WEB-INF/classes/org/wildfly/extension/vertx/test/mini/
/WEB-INF/classes/org/wildfly/extension/vertx/test/mini/deployment/
/WEB-INF/classes/org/wildfly/extension/vertx/test/mini/deployment/TestVerticle.class

The content of the deployment descriptor(WEB-INF/vertx.json) is:

Deployment descriptor content
{
  "deployments": [
    {
      "verticle-class": "org.wildfly.extension.vertx.test.mini.deployment.TestVerticle"
    }
  ]
}

when the war is deployed, the TestVerticle will be deployed to the Vert.x instance using the default DeploymentOptions.

These deployed verticles inside the Vert.x instance will be un-deployed when the deployment gets un-deployed from WildFly server.

6.1. Deployment Descriptor

The deployment descriptor to be able to deploy the verticle is either at META-INF/vertx.json or WEB-INF/vertx.json.

A full example of vertx.json is:

Full example of vertx.json
{
  "ver": 1,
  "deployments": [
    {
      "verticle-class": "org.wildfly.extension.vertx.test.mini.deployment.TestVerticle",
      "deploy-options": {}
    }
  ]
}
  • ver - The version of the deployment descriptor, it is 1, and it can be skipped now.

  • deployments - An array of the deployments, each of which is a json structure.

    • verticle-class - The FQCN of the verticle class which will be deployed.

    • deploy-options - The DeploymentOptions which is used to do the deployment.