Chapter 4. WildFly Swarm for Microservices

The last Java microservice framework we’ll look at is a relative newcomer to the scene yet leverages tried-and-trusted Java EE functionality found in the JBoss WildFly application server. WildFly Swarm is a complete teardown of the WildFly application server into bite-sized, reusable components called fractions that can be assembled and formed into a microservice application that leverages Java EE APIs. Assembling these fractions is as simple as including a dependency in your Java Maven (or Gradle) build file, and WildFly Swarm takes care of the rest.

Application servers and Java EE have been the workhorse of enterprise Java applications for more than 15 years. WildFly (formerly JBoss Application Server) emerged as an enterprise-capable, open source application server. Many enterprises heavily invested in the Java EE technology (whether open source or proprietary vendors) from how they hire software talent as well as overall training, tooling, and management. Java EE has always been very capable at helping developers build tiered applications by offering functionality like servlets/JSPs, transactions, component models, messaging, and persistence. Deployments of Java EE applications were packaged as EARs, which typically contained many WARs, JARs, and associated configuration. Once you had your Java archive file (EAR/WAR), you would need to find a server, verify it was configured the way you expect, and then install your archive. You could even take advantage of dynamic deployment and redeployment (although doing this in production is not recommended, it can be useful in development). This meant your archives could be fairly lean and only include the business code you needed. Unfortunately, this lead to bloated implementations of Java EE servers that had to account for any functionality that an application might need. It also led to over-optimization in terms of which dependencies to share (just put everything in the app server!) and which dependencies needed isolation because they would change at a different rate from other applications.

image

The application server provided a single point of surface area for managing, deploying, and configuring multiple applications within a single instance of the app server. Typically you’d cluster these for high availability by creating exact instances of the app server on different nodes. The problems start to arise when too many applications share a single deployment model, a single process, and a single JVM. The impedance arises when multiple teams who develop the applications running inside the app server have different types of applications, velocities of change, performance or SLA needs, and so on. Insofar as microservices architecture enables rapid change, innovation, and autonomy, Java EE application servers and managing a collection of applications as a single, all-in-one server don’t enable rapid change. Additionally, from the operations side of the house, it becomes very complex to accurately manage and monitor the services and applications running within a single application server. In theory a single JVM is easier to manage, since it’s just one thing, but the applications within the JVM are all independent deployments and should be treated as such. We can feel this pain when we try to treat the individual applications and services within a single process as “one thing,” which is why we have very expensive and complicated tooling to try and accomplish that introspection. One way teams get around some of these issues is by deploying a single application to an application server.

Even though the deployment and management of applications within a Java EE environment may not suit a microservices environment, the component models, APIs, and libraries that Java EE provides to application developers still provide a lot of value. We still want to be able to use persistence, transactions, security, dependency injection, etc., but we want an à la carte usage of those libraries where needed. So how do we leverage our knowledge of Java EE, the power it brings within the context of microservices? That’s where WildFly Swarm fits in.

WildFly Swarm evaluates your pom.xml (or Gradle file) and determines what Java EE dependencies your microservice actually uses (e.g., CDI, messaging, and servlet) and then builds an uber JAR (just like Spring Boot and Dropwizard) that includes the minimal Java EE APIs and implementations necessary to run your service. This process is known as “just enough application server,” which allows you to continue to use the Java EE APIs you know and love and to deploy them both in a microservices and traditional-application style. You can even just start using your existing WAR projects and WildFly Swarm can introspect them automatically and properly include the requisite Java EE APIs/fractions without having to explicitly specify them. This is a very powerful way to move your existing applications to a microservice-style deployment.

Getting Started

There are three ways to get started with WildFly Swarm. You can start out with a blank Java Maven or Gradle project and manually add dependencies and Maven plug-ins. Another option is to use the WildFly Swarm Generator web console to bootstrap your project (similar to Spring Initializr for Spring Boot). Lastly, you can use the JBoss Forge tool, which is a generic Java project creation and altering tool which makes it easy to add Java classes, dependencies, and entire classes of functionality (e.g., JPA and transactions) to a Java Maven project. We highly recommend JBoss Forge in general, and we will use it in the guide here. For completeness, we’ll also include the minimal plug-ins and dependencies you might need for a vanilla Java project. JBoss Forge also has plug-ins for the three most popular Java IDEs (Eclipse, Netbeans, or IntelliJ).

Vanilla Java Project

If you have an existing Java project or you create one from scratch using a Maven archetype or your favorite IDE, then you can add the following pieces to your pom.xml to get up and running with WildFly Swarm. First we want to be able to create uber JARs that know what pieces of the Java EE API functionality should be included. To do this, we’ll use the WildFly Swarm Maven plug-in. Let’s add the WildFly Swarm Maven plug-in to our pom.xml:

<plugins>
  <plugin>
    <groupId>org.wildfly.swarm</groupId>
    <artifactId>wildfly-swarm-plugin</artifactId>
    <version>${version.wildfly.swarm}</version>
    <executions>
      <execution>
        <goals>
          <goal>package</goal>
        </goals>
      </execution>
    </executions>
  </plugin>
</plugins>

We also want to include the WildFly Swarm BOM (bill of materials) as a dependency in our <dependencyManagement> section to help sort out the proper versions of all of the APIs and WildFly Swarm fractions that we may depend on:

<dependencyManagement>
    <dependencies>
        <!-- JBoss distributes a complete set of Java EE 7 APIs
            including a Bill of Materials (BOM). A BOM specifies
            the versions of a "stack" (or a collection) of
            artifacts. We use this here so that we always get
            the correct versions of artifacts. -->
        <dependency>
            <groupId>org.wildfly.swarm</groupId>
            <artifactId>bom</artifactId>
            <version>${version.wildfly.swarm}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Now you can add the fractions of the Java EE API you need (or leave the fractions out and let WildFly Swarm autodetect them; useful if migrating an existing WAR application)! Let’s take a look at some of the convenience that JBoss Forge brings.

Using JBoss Forge

JBoss Forge is a set of IDE plug-ins and CLI for quickly creating and working on Java projects. It has plug-ins for Netbeans, Eclipse, and IntelliJ to help you create Java projects, add CDI beans, add JPA entities, add and configure servlets, etc. Let’s look at a quick example. First verify you have JDK/Java 1.8 installed then install JBoss Forge.

Once you have Forge installed, you should be able to start up the CLI (all of these commands available in the IDE plug-in as well):

$ forge

Feel free to explore what commands are available by pressing Tab, which also gives auto-completion for any command. JBoss Forge is built on a modular, plug-in–based architecture which allows others to write plug-ins to take advantage of the built-in tooling for the CLI and your favorite IDE. Take a look at some of the addons contributed by the community, including AsciiDoctor, Twitter, Arquillian, and AssertJ. Let’s also install the WildFly Swarm addon for JBoss Forge:

[temp]$ addon-install \
--coordinate org.jboss.forge.addon:wildfly-swarm,1.0.0.Beta2

***SUCCESS*** Addon org.jboss.forge.addon:wildfly-swarm,
1.0.0.Beta2 was installed successfully.

Let’s try a project-new command to build a new Java EE project that will be built and packaged with WildFly Swarm. Follow the interactive command prompt with the following inputs:

[swarm]$ project-new
***INFO*** Required inputs not satisfied, interactive mode
* Project name:  hola-wildflyswarm
? Package [org.hola.wildflyswarm]: com.redhat.examples.wfswarm
? Version [1.0.0-SNAPSHOT]:  1.0
? Final name:  hola-wildflyswarm
? Project location [/Users/ceposta/temp/swarm]:

[0] (x) war
[1] ( ) jar
[2] ( ) parent
[3] ( ) forge-addon
[4] ( ) resource-jar
[5] ( ) ear
[6] ( ) from-archetype
[7] ( ) generic

Press <ENTER> to confirm, or <CTRL>+C to cancel.
* Project type: [0-7]

[0] (x) Maven

Press <ENTER> to confirm, or <CTRL>+C to cancel.
* Build system: [0]

[0] ( ) JAVA_EE_7
[1] ( ) JAVA_EE_6
[2] ( ) NONE

Press <ENTER> to confirm, or <CTRL>+C to cancel.
? Stack (The technology stack to be used in project): [0-2] 2
***SUCCESS*** Project named 'hola-wildflyswarm'
has been created.

So what we have right now is an empty Java project that doesn’t do too much. That’s OK, though; we’re just getting started. Let’s set it up for a JAX-RS application:

[hola-wildflyswarm]$ rest-setup --application-path=/
***SUCCESS*** JAX-RS has been installed.

Now, let’s add in the WildFly Swarm configurations like the Maven plug-in and the BOM dependency management section:

[hola-wildflyswarm]$ wildfly-swarm-setup --context-path=/
***SUCCESS*** Wildfly Swarm is now set up! Enjoy!

That’s it! Now let’s build and try to run our new WildFly Swarm microservice:

[HelloResource.java]$ cd ~~
[hola-wildflyswarm]$ wildfly-swarm-run

You should see it successfully start, but it doesn’t do anything or expose any REST services. But what did JBoss Forge create for us here? If you look at the directory structure, you should see something similar:

./pom.xml
./src
./src/main
./src/main/java
./src/main/java/com/redhat/examples/wfswarm/rest
    /RestApplication.java
./src/main/resources
./src/main/webapp
./src/test
./src/test/java
./src/test/resources

Pretty bare bones! If we look at the pom.xml, we see some relevant Java EE APIs and the WildFly Swarm plug-in/BOM:

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.wildfly.swarm</groupId>
        <artifactId>bom</artifactId>
        <version>${version.wildfly-swarm}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>com.redhat.spec</groupId>
        <artifactId>jboss-javaee-6.0</artifactId>
        <version>3.0.3.Final</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.redhat.spec.javax.servlet</groupId>
        <artifactId>jboss-servlet-api_3.0_spec</artifactId>
        <scope>provided</scope>
      </dependency>
      <dependency>
        <groupId>com.redhat.spec.javax.ws.rs</groupId>
        <artifactId>jboss-jaxrs-api_1.1_spec</artifactId>
        <scope>provided</scope>
      </dependency>
    </dependencies>
      <build>
        <finalName>hola-wildflyswarm</finalName>
        <plugins>
          <plugin>
            <groupId>org.wildfly.swarm</groupId>
            <artifactId>wildfly-swarm-plugin</artifactId>
            <version>${version.wildfly-swarm}</version>
            <executions>
              <execution>
                <goals>
                  <goal>package</goal>
                </goals>
              </execution>
            </executions>
            <configuration>
              <properties>
                <swarm.context.path>/</swarm.context.path>
              </properties>
            </configuration>
          </plugin>
        </plugins>
      </build>

Remember, however, WildFly Swarm will only package the pieces of the Java EE framework that you need to run your application. In this case, we’ve already set up JAX-RS APIs, so WildFly Swarm will automatically include the JAX-RS and servlet fractions of an application server and embed them in your application.

Let’s see how we can add some more functionality.

Hello World

Just like with the other frameworks in the preceding chapters, we want to add some basic hello-world functionality and then incrementally add more functionality on top of it. Let’s start by creating a HolaResource in our project. You can do this with your IDE, or however you’d like; but again we can leverage JBoss Forge to do any of the heavy lifting for us here.

Navigate to the directory where you have your project, and fire up forge if it’s not already running:

$ forge

Add the HTTP Endpoints

Now let’s create a new JAX-RS endpoint with the rest-new-endpoint command and the interactive wizard, filling in the prompts using the following example as guidance:

[hola-wildflyswarm]$ rest-new-endpoint
***INFO*** Required inputs not satisfied, interactive mode
? Package Name (The package name where type will be created) \
[com.redhat.examples.wfswarm.rest]:

* Type Name (The type name):  HolaResource

[0] (x) GET
[1] ( ) POST
[2] ( ) PUT
[3] ( ) DELETE

Press <ENTER> to confirm, or <CTRL>+C to cancel.
? Methods (REST methods to be defined): [0-3]
? Path (The root path of the endpoint):  api/hola
***SUCCESS***
REST com.redhat.examples.wfswarm.rest.HolaResource created

That’s it! Forge has created the ./src/main/java/com/redhat/examples/wfswarm/rest/HolaResource.java JAX-RS resource for us, and it looks similar to this:

package com.redhat.examples.wfswarm.rest;

import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import javax.ws.rs.GET;
import javax.ws.rs.Produces;

@Path("/api/hola")
public class HolaResource {

        @GET
        @Produces("text/plain")
        public Response doGet() {
                return Response.ok("method doGet invoked")
                               .build();
        }
}

Let’s go to the root of the project, build it, and try to fire it up again:

[HelloResource.java]$ cd ~~
[hola-wildflyswarm]$ wildfly-swarm-run

And navigate in a web browser to http://localhost:8080/api/hola (if an endpoint is not correctly displayed at this endpoint, please go back and check the preceding steps):

successful german

What did we just do? We built a JAX-RS web application using native Java EE with the JBoss Forge tooling and then ran it as a microservice inside WildFly Swarm!

Externalize Configuration

At the time of this writing, WildFly Swarm does not have an opinionated way of doing configuration and folks can choose to use well-established configuration, frameworks like Apache Commons Configuration or Apache DeltaSpike Configuration. Feel free to pay attention to this JIRA thread for more. In this section, we’ll take a look at quickly adding Apache DeltaSpike Configuration for our configuration needs.

Apache DeltaSpike is a collection of CDI extensions that can be used to simplify things like configuration, data access, and security. Take a look at the DeltaSpike documentation. We’re going to use a CDI extension that lets us easily inject configuration properties that can be sourced from properties files, the command line, JNDI, and environment variables. To take advantage of CDI, JAX-RS, and DeltaSpike, let’s add a dependency on the jaxrs-cdi WildFly Swarm Fraction for integrating CDI and JAX-RS:

    <dependency>
      <groupId>org.wildfly.swarm</groupId>
      <artifactId>jaxrs-cdi</artifactId>
    </dependency>

We’ll also want to add a dependency on the DeltaSpike libraries:

    <dependency>
      <groupId>org.apache.deltaspike.core</groupId>
      <artifactId>deltaspike-core-api</artifactId>
      <version>1.5.3</version>
    </dependency>
    <dependency>
      <groupId>org.apache.deltaspike.core</groupId>
      <artifactId>deltaspike-core-impl</artifactId>
      <version>1.5.3</version>
    </dependency>

We can create a new file called META-INF/apache-deltaspike.properties as well to store our application-specific properties. In this example, we’ll try to grab our environment-specific properties from OS environment variables like we’ve been doing with the other frameworks and then default to values that may not exist. Edit your HolaResource class to add the @ConfigProperty annotation:

@Path("/api/hola")
public class HolaResource {

    @Inject
    @ConfigProperty(name = "WF_SWARM_SAYING",
        defaultValue = "Hola")
    private String saying;

    @GET
    @Produces("text/plain")
    public Response doGet() {
        return Response.ok(saying + " from WF Swarm").build();
    }
}

With this simple annotation, we’re able to quickly inject our properties from either the META-INF/apache-deltaspike.properties file, from the command line, from environment variables, or from JNDI. We will default to “Hola” if there is no environmental variable set. Take a look at the Apache DeltaSpike documentation for more ways to group or customize the functionality.

Now we can run our service with either java -jar target/hola-wildflyswarm-swarm.jar or with mvn clean install wildfly-swarm:run. We should see the default response “Hola from WF Swarm” and if we set the environment variable WF_SWARM_SAYING, then we should be able to alter the saying:

$ mvn clean install wildfly-swarm:run
successful german
$ export WF_SWARM_SAYING=Yo
$ mvn clean install wildfly-swarm:run
successful german

Expose Application Metrics and Information

To expose useful information about our microserivce, all we need to do is add the monitor fraction to our pom.xml:

    <dependency>
      <groupId>org.wildfly.swarm</groupId>
      <artifactId>monitor</artifactId>
    </dependency>

This will enable the WildFly management and monitoring functionality. From the monitoring perspective, WildFly Swarm exposes some basic metrics:

  • Information about the node on which WildFly Swarm is running at /node

  • JVM heap usage at /heap

  • JVM/process thread information at /threads

We can also add our own health endpoints that can invoke some actions or query parts of our microservice to easily expose how our service is doing. You can leverage the built-in health checking probes of most modern clusters to call your microservice health endpoints to determine whether or not the microservice is healthy, and in some cases, just kill it and restart. See the WildFly Swarm documentation for more on adding health probes.

How to Run This Outside of Maven

We’ve seen a couple ways to run our WildFly Swarm microservice. For development, you’ll probably run with the Maven plug-in like this:

$ mvn clean install wildfly-swarm:run

When you build the binary, you can run it like this:

$ mvn clean package

This will take our project, whether it’s packaged as a JAR or a WAR (as specified by the <packaging> definition in your pom.xml) and turn it into an executable uber JAR. Then you can run it like this:

$ java -jar target/hola-wildfly-swarm.jar

Note, whatever your final build-artifact is named, the WildFly Swarm Maven plug-in will add the -swarm.jar extension to the name.

Calling Another Service

In a microservice environment, each service is responsible for providing the functionality or service to other collaborators. If we wish to extend the “hello world” microservice, we will need to create a service to which we can call using JAX-RS client functionality. Just like we did for the Spring Boot microservice, we’ll leverage the backend service from the source code that accompanies the book. The interaction will look similar to this:

successful german

If you look in this book’s source code, we’ll see a Maven module called backend which contains a very simple HTTP servlet that can be invoked with a GET request and query parameters. The code for this backend is very simple, and does not use any of the microservice frameworks (Spring Boot, Dropwizard, or WildFly Swarm).

To start up the backend service on port 8080, navigate to the backend directory and run the following:

$ mvn clean install jetty:run

This service is exposed at /api/backend and takes a query parameter greeting. For example, when we call this service with this path /api/backend?greeting=Hello, then the backend service will respond with a JSON object like this:

$ curl -X GET http://localhost:8080/api/backend?greeting=Hello

We get something like this:

{
  "greeting" : "Hello from cluster Backend",
  "time" : 1459189860895,
  "ip" : "172.20.10.3"
}

We will create a new HTTP endpoint, /api/greeting in our WildFly Swarm hola-wildflyswarm example and use JAX-RS client to call this backend!

Create a new class in src/main/java/com/redhat/examples/wfswarm/rest called GreeterResource, and fill it in similar to what we did for the HolaResource like in Example 4-1.

Example 4-1. src/main/java/com/redhat/examples/wfswarm/rest/GreeterResource.java
@Path("/api")
public class GreeterResource {

    @Inject
    @ConfigProperty(name = "WF_SWARM_SAYING",
        defaultValue = "Hola")
    private String saying;

    @Inject
    @ConfigProperty(name = "GREETING_BACKEND_SERVICE_HOST",
        defaultValue = "localhost")
    private String backendServiceHost;

    @Inject
    @ConfigProperty(name = "GREETING_BACKEND_SERVICE_PORT",
        defaultValue = "8080")
    private int backendServicePort;

    @Path("/greeting")
    @GET
    public String greeting() {
        String backendServiceUrl = String.format(
            "http://%s:%d",
            backendServiceHost,backendServicePort);

        System.out.println("Sending to: " + backendServiceUrl);

        return backendServiceUrl;
    }
}

We’ve created a simple JAX-RS resource here that exposes an /api/greeting endpoint that just returns the value of the backendServiceUrl field. Also note, we’re injecting the backend host and port as environment variables that have default values if no environment variables are set. Again, we’re just using DeltaSpike @ConfigProperty to accomplish this.

Let’s also add the BackendDTO class as shown in Example 4-2, which is used to encapsulate responses from the backend.

Example 4-2. src/main/java/com/redhat/examples/wfswarm/rest/BackendDTO.java
public class BackendDTO {

    private String greeting;
    private long time;
    private String ip;

    public String getGreeting() {
        return greeting;
    }

    public void setGreeting(String greeting) {
        this.greeting = greeting;
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }
}

Next, let’s add our JAX-RS client implementation to communicate with the backend service. It should look like Example 4-3.

Example 4-3. src/main/java/com/redhat/examples/wfswarm/rest/GreeterResource.java
@Path("/api")
public class GreeterResource {

    @Inject
    @ConfigProperty(name = "WF_SWARM_SAYING",
        defaultValue = "Hola")
    private String saying;

    @Inject
    @ConfigProperty(name = "GREETING_BACKEND_SERVICE_HOST",
        defaultValue = "localhost")
    private String backendServiceHost;

    @Inject
    @ConfigProperty(name = "GREETING_BACKEND_SERVICE_PORT",
        defaultValue = "8080")
    private int backendServicePort;

    @Path("/greeting")
    @GET
    public String greeting() {
        String backendServiceUrl = String.format("http://%s:%d",
            backendServiceHost,backendServicePort);

        System.out.println("Sending to: " + backendServiceUrl);

        Client client = ClientBuilder.newClient();
        BackendDTO backendDTO = client.target(backendServiceUrl)
                .path("api")
                .path("backend")
                .queryParam("greeting", saying)
                .request(MediaType.APPLICATION_JSON_TYPE)
                .get(BackendDTO.class);

        return backendDTO.getGreeting()
            + " at host: " + backendDTO.getIp();
    }
}

Now we can build our microservice either using Maven at the command line; or if you’re still in JBoss Forge, you can run the build command:

$ mvn clean install

or:

[hola-wildflyswarm]$ build

When we start up our WildFly Swarm microservice, we will need to specify a new HTTP port (since the backend service is already running on port 8080), or we can just specify a port offset. If we specify a port offset, WildFly Swarm will try to deploy under its default port of 8080; but if that port is already in use, then it will increment the port by the swarm.port.offset amount and try again. If we use an offset of 1, and there is a collision on port 8080, then port 8081 will be what WildFly Swarm tries next. Let’s run our microservice with a port offset:

$ mvn clean install wildfly-swarm:run -Dswarm.port.offset=1

Now, let’s navigate our browser to http://localhost:8081/api/greeting to see if our microservice properly calls the backend and displays what we’re expecting:

successful german

Where to Look Next

In this chapter, we learned about WildFly Swarm and saw some differences and similarities with Dropwizard and Spring Boot. We also learned how to expose REST endpoints, configuration, and metrics and make calls to external services. This was meant as a quick introduction to WildFly Swarm and is by no means a comprehensive guide. Check out the following links for more information:

Get Microservices for Java Developers now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.