Starting with Vert.x 3 – Creating a new project
April 11, 2015

These are my first impressions on what have changed in Vert.x 3 from a regular java developer perspective.

Vert.x 3 has ripped off the old modules concepts in favor of a more straightforward approach. Now you don’t need to create a .zip file with an structure to deploy your application, instead, you run your application inside vert.x however you want. These is a great power and at first I found myself a little lost.

You can still download a vert.x distribution but, with the current state of the platform, I see it like an easy way to start making experiments (even in the many languages vert.x support) and it’s not necessary anymore for productive environments.

In Vert.x 3 you create your own main function, invoke a Vert.x factory, and voilĂ , you have the whole platform there to serve you. Indeed, you can see the io.vertx.core.Starter class, which is invoked by the executable bin/vertx in the distribution, to see there is nothing but CLI args parsing and building configurations for the factory I mentioned before.

From the v2 era I liked to have a main point written in javascript to deploying all my java verticles. I though it as my init script so it made sense to have it in an scripting language, I didn’t want to lose that. So now it’s my responsibility to create this structure. A few lines of code is worth a thousand words:
(Deprecated: see Update section below)

    public class Launcher {
        public static void main(String[] args) {
            Logger vertxLogger = LoggerFactory.getLogger(Launcher.class.getName());
            Vertx vertx = Vertx.vertx();
            vertx.deployVerticle("main.js", event -> {
                if (event.succeeded()) {
                    vertxLogger.info("Your Vert.x application is started!");
                } else {
                    vertxLogger.error("Unable to start your application", event.cause());
                }
            });

            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    final CountDownLatch latch = new CountDownLatch(1);
                    vertx.close(ar -> {
                        if (ar.succeeded()) {
                            vertxLogger.info("Your Vert.x application is stopped!");
                        } else {
                            vertxLogger.error("Failure in stopping Vert.x", ar.cause());
                        }
                        latch.countDown();
                    });
                    try {
                        if (!latch.await(2, TimeUnit.MINUTES)) {
                            vertxLogger.error("Timed out waiting to undeploy all");
                        }
                    } catch (InterruptedException e) {
                        throw new IllegalStateException(e);
                    }
                }
            });
        }
    }

In line 5 I deploy my main verticle.
Lines 13-32 are stolen from io.vertx.core.Starter to properly shutdown the application.

For this to run you just need a few dependencies: io.vertx:vertx-core:jar:3.0.0-milestone4:compile and io.vertx:vertx-lang-js:jar:3.0.0-milestone4:runtime

Packing your application

Now it’s just a matter of packing the application to make it runnable. There is a movement loving the concept of fatJar, I personally dislike it for various reasons, one of them is that I see it completely unnecessary, we are advanced Java developers, man up, setup you classpath and invoke the Main Class as the good lord says (well, that was too much).

In Maven this is extra easy, you just need to properly setup the maven-jar-plugin to write the MANIFEST and the maven-assembly-plugin to load an assembly descriptor. Then you can have a simple run.sh script for running it without even having to remember the name of your main jar. I use something like this:

    #!/usr/bin/env bash
    DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
    java -jar $DIR/libs/${project.build.finalName}.${project.packaging} $@

Here is a minimal pom.xml briefing all the above ideas:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

      <modelVersion>4.0.0</modelVersion>
      <groupId>com.locademiaz</groupId>
      <artifactId>initial-vertx-3-project</artifactId>
      <version>0.1-SNAPSHOT</version>
      <packaging>jar</packaging>
    
      <dependencies>
        <dependency>
          <groupId>io.vertx</groupId>
          <artifactId>vertx-core</artifactId>
          <version>${vertx.version}</version>
        </dependency>
        <dependency>
          <groupId>io.vertx</groupId>
          <artifactId>vertx-lang-js</artifactId>
          <version>${vertx.version}</version>
          <scope>runtime</scope>
        </dependency>
      </dependencies>
    
      <build>
        <resources>
          <resource>
            <directory>src/main/js</directory>
          </resource>
        </resources>
        <plugins>
          <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.3</version>
            <configuration>
              <source>1.8</source>
              <target>1.8</target>
            </configuration>
          </plugin>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>2.6</version>
            <configuration>
              <archive>
                <manifest>
                  <mainClass>com.locademiaz.Launcher</mainClass>
                  <addClasspath>true</addClasspath>
                </manifest>
              </archive>
            </configuration>
          </plugin>
          <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>2.5.3</version>
            <configuration>
              <descriptor>src/main/assembly/dist.xml</descriptor>
            </configuration>
            <executions>
              <execution>
                <id>make-assembly</id>
                <phase>package</phase>
                <goals>
                  <goal>single</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>

      <properties>
        <vertx.version>3.0.0-milestone4</vertx.version>
      </properties>
    </project>

And also the dist.xml:

    <assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
      <id>dist</id>
      <formats>
        <format>tar.gz</format>
      </formats>
      <baseDirectory>${project.artifactId}</baseDirectory>
      <dependencySets>
        <dependencySet>
          <outputDirectory>libs/</outputDirectory>
          <scope>runtime</scope>
        </dependencySet>
      </dependencySets>
      <files>
        <file>
          <source>${project.build.directory}/${project.build.finalName}.${project.packaging}</source>
          <outputDirectory>libs/</outputDirectory>
          <fileMode>0664</fileMode>
        </file>
        <file>
          <source>src/main/sh/run.sh</source>
          <fileMode>0744</fileMode>
          <filtered>true</filtered>
        </file>
      </files>
    </assembly>

With this initial configuration, you run mvn package and you’ll got a tarball into the target/ folder, untar it and you’ll have a nice running script for your whole application.

Bonus track

I prefer to use Logback as my logging system, and in the old days we had to modify the vert.x distribution and the executable to get this support.
Nowadays you just add System.setProperty(LoggerFactory.LOGGER_DELEGATE_FACTORY_CLASS_NAME, SLF4JLogDelegateFactory.class.getName()); as the first line of your main, add the ch.qos.logback:logback-classic:jar:1.1.3:runtime dependency and you are there.
For the extra lazy, here is a simple logback.xml:

    <configuration>
      <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender" level="INFO">
        <encoder>
          <pattern>[%-5level] %logger{15} - %msg%n</pattern>
        </encoder>
      </appender>
    
      <logger name="io.vertx" level="INFO">
        <appender-ref ref="STDOUT" />
      </logger>
      <logger name="com.hazelcast" level="ERROR">
        <appender-ref ref="STDOUT" />
      </logger>
      <logger name="io.netty.util.internal.PlatformDependent" level="ERROR">
        <appender-ref ref="STDOUT" />
      </logger>
      <root level="INFO">
      </root>
    </configuration>

I also like to have an user accessible logging configuration, for this, you must add the following to the dist.xml file

        <file>
          <source>src/main/resources/logback.xml</source>
          <fileMode>0644</fileMode>
        </file>

and modify the java invocation line in run.sh to include -Dlogback.configurationFile=file:$DIR/logback.xml

I hope you find this useful and enjoy the great things Vert.x 3 has to offer.

UPDATE:

I’ve been thinking and I rather see unnecessary to have my own main class which at the end will imitate the provided io.vertx.core.Starter. So I’ve simplified my initial deployment removing all references to my custom mainClass and with this run.sh

    #!/usr/bin/env bash
    DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
    java -Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.impl.SLF4JLogDelegateFactory \
         -Dlogback.configurationFile=file:$DIR/logback.xml \
         -classpath "$DIR/libs/*" io.vertx.core.Starter run main.js \
         -conf $DIR/conf.json

Also, if you want to run your application using maven (useful for debugging) you just need to configure the org.codehaus.mojo:exec-maven-plugin like this:

    <plugin>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>exec-maven-plugin</artifactId>
      <version>1.4.0</version>
      <configuration>
        <mainClass>io.vertx.core.Starter</mainClass>
        <systemProperties>
          <systemProperty>
            <key>vertx.logger-delegate-factory-class-name</key>
            <value>io.vertx.core.logging.impl.SLF4JLogDelegateFactory</value>
          </systemProperty>
        </systemProperties>
        <arguments>
          <argument>run</argument>
          <argument>main.js</argument>
          <argument>-conf</argument>
          <argument>${project.build.outputDirectory}/conf.json</argument>
        </arguments>
      </configuration>
    </plugin>
Advertisements