CLOVER🍀

That was when it all began.

はじめてのWildFly Swarm

前々から気になっていたWildFly Swarmですが、そろそろちょっと試してみることにしました。

Rightsize your Java EE Applications | Thorntail

WildFly Swarmって?

WildFlyを組み込んで、実行可能JARを作成したり、必要な機能やその他のライブラリなどを統合した機能を提供してくれる仕組み。

詳しくは、こちらの資料などを見ていただくとよいかと。

WildFly Swarm - Rightsize Your Java EE Apps from Yoshimasa Tanabe

また、ドキュメントはこちら。

WildFly Swarm User’s Guide

こちらも、合わせて読むとよいでしょう。

WildFly Swarm Tour

サンプルリポジトリもあるので、適宜参考に。
https://github.com/wildfly-swarm/wildfly-swarm-examples

自分がWildFly Swarmを使うモチベーション

アプリケーションサーバーにWARファイルをデプロイするのが面倒。

これに尽きます。

なので、uberjarとかMicroserviesとかの高尚な(?)話は割とよくて、

ふつうに作ったJava EEアプリケーションを簡単に起動したい、というのがまずは使ってみたい動機です。

またMicroProfile Serverという、JARファイルにWARファイルを与えて起動できるというものも登場したので(Payara Microっぽいですね)、こちらを使いたいなぁと。

WildFly Swarmのuberjar、でかいんですよ…。

なので、作るWARファイルはまずはふつうのJava EEの範囲でのものを考えます。WildFly Swarmならではのものは、そのうち気が向いたら試していくかも?

使ってみる

というわけで、この目的でWildFly Swarmを使ってアプリケーションを動かしてみます。今回は、JAX-RSCDIで簡単なアプリケーションを書いて、最後にArquillianでテストまでしてみます。

Mavenの設定は、このように用意。
pom.xml

<?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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.littlewings</groupId>
    <artifactId>wildfly-swarm-mp-jaxrs-cdi</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <failOnMissingWebXml>false</failOnMissingWebXml>

        <wildfly.swarm.version>2016.9</wildfly.swarm.version>
        <arquillian.version>1.1.10.Final</arquillian.version>
        <scala.major.version>2.11</scala.major.version>
        <scala.version>${scala.major.version}.8</scala.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.wildfly.swarm</groupId>
                <artifactId>bom-all</artifactId>
                <version>${wildfly.swarm.version}</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
            <dependency>
                <groupId>org.jboss.arquillian</groupId>
                <artifactId>arquillian-bom</artifactId>
                <version>${arquillian.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>

        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-client</artifactId>
            <version>3.0.19.Final</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.wildfly.swarm</groupId>
            <artifactId>jaxrs-cdi</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.wildfly.swarm</groupId>
            <artifactId>arquillian</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.junit</groupId>
            <artifactId>arquillian-junit-container</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.scalatest</groupId>
            <artifactId>scalatest_${scala.major.version}</artifactId>
            <version>3.0.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>app</finalName>
        <plugins>
            <plugin>
                <groupId>org.wildfly.swarm</groupId>
                <artifactId>wildfly-swarm-plugin</artifactId>
                <version>${wildfly.swarm.version}</version>
                <!--
                <executions>
                    <execution>
                        <goals>
                            <goal>package</goal>
                        </goals>
                    </execution>
                </executions>
                -->
            </plugin>
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>3.2.2</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <scalaVersion>${scala.version}</scalaVersion>
                    <args>
                        <arg>-Xlint</arg>
                        <arg>-unchecked</arg>
                        <arg>-deprecation</arg>
                        <arg>-feature</arg>
                    </args>
                    <recompileMode>incremental</recompileMode>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Scalaが入っているのは、放っておいてください(笑)。

BOMとして、WildFly SwarmのものとArquillianのものを入れておきます。

providedスコープにJava EEAPI(とScala)を追加。あとは、テスト用のものです。WildFly Swarmのjaxrs-cdiがtestスコープに混じっていますが、これはテストの起動時には要るから…。

また、JAX-RSのリソースクラスに対して、HTTP経由でテストするため、RESTEasyのClient APIも入れています。

WildFly SwarmのMaven Pluginですが、uberjarは作らないのでexecutionsのところはコメントアウト

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

ビルドしてできあがるWARファイルの名前は、「app.war」とします。

        <finalName>app</finalName>

サンプルアプリケーション

足し算をするだけの、簡単なアプリケーションを作ります。

JAX-RSの有効化。
src/main/scala/org/littlewings/javaee7/microprofile/rest/JaxrsActivator.scala

package org.littlewings.javaee7.microprofile.rest

import javax.ws.rs.ApplicationPath
import javax.ws.rs.core.Application

@ApplicationPath("rest")
class JaxrsActivator extends Application

JAX-RSリソースクラス。
src/main/scala/org/littlewings/javaee7/microprofile/rest/CalcResource.scala

package org.littlewings.javaee7.microprofile.rest

import javax.enterprise.context.RequestScoped
import javax.inject.Inject
import javax.ws.rs.core.MediaType
import javax.ws.rs.{GET, Path, Produces, QueryParam}

import org.littlewings.javaee7.microprofile.service.CalcService

@Path("calc")
@RequestScoped
class CalcResource {
  @Inject
  var calcService: CalcService = _

  @Path("add")
  @GET
  @Produces(Array(MediaType.TEXT_PLAIN))
  def add(@QueryParam("a") a: Int, @QueryParam("b") b: Int): Int =
    calcService.add(a, b)
}

CDI管理Bean。
src/main/scala/org/littlewings/javaee7/microprofile/service/CalcService.scala

package org.littlewings.javaee7.microprofile.service

import javax.enterprise.context.ApplicationScoped

@ApplicationScoped
class CalcService {
  def add(a: Int, b: Int): Int = a + b
}

とまあ、こんな感じで。特にWildFly Swarmに依存するコードは入っていません。

実行

いくつか方法がありますが、まずはWildFly SwarmのMaven Pluginで起動してみます。

$ mvn wildfly-swarm:run

確認。

$ curl 'http://localhost:8080/rest/calc/add?a=5&b=3'
8

OKですね。

次に、MicroProfile Serverで起動してみます。ダウンロードページから、「microprofile-hollowswarm.jar」をダウンロードします。

http://wildfly-swarm.io/downloads/

現時点では、「microprofile-2016.9-hollowswarm.jar」というものになります。

WARファイルを作ります。

$ mvn package

WARファイルを指定して、「java -jar」で起動。

$ java -jar microprofile-2016.9-hollowswarm.jar target/app.war

確認。この場合は、URLにコンテキストパス(今回は「app」)が入ります。

$ curl 'http://localhost:8080/app/rest/calc/add?a=5&b=3'
8

uberjarで実行したい場合は、WildFly SwarmのMaven Pluginをpackage時に実行するように仕込んでください。

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

これで、「mvn package」でuberjarができあがります。

テストをする

最後に、Arquillianを使ってテストをします。

特にArquillianの設定ファイルなどを用意しなくても、実行可能です。

Testing with Arquillian

Arquillian を用いた Integration Test

それでは、テストを用意します。

JAX-RS側のテストコード。こちらは、@RunAsClientでクライアントとして動かします。
src/test/scala/org/littlewings/javaee7/microprofile/rest/CalcResourceTest.scala

package org.littlewings.javaee7.microprofile.rest

import java.net.URL
import javax.ws.rs.ApplicationPath
import javax.ws.rs.client.ClientBuilder

import org.jboss.arquillian.container.test.api.{Deployment, RunAsClient}
import org.jboss.arquillian.junit.Arquillian
import org.jboss.arquillian.test.api.ArquillianResource
import org.jboss.shrinkwrap.api.ShrinkWrap
import org.jboss.shrinkwrap.resolver.api.maven.Maven
import org.junit.Test
import org.junit.runner.RunWith
import org.littlewings.javaee7.microprofile.service.CalcService
import org.scalatest.Matchers
import org.wildfly.swarm.Swarm
import org.wildfly.swarm.arquillian.CreateSwarm
import org.wildfly.swarm.jaxrs.JAXRSArchive

object CalcResourceTest {
  @Deployment
  def createDeployment: JAXRSArchive =
    ShrinkWrap
      .create(classOf[JAXRSArchive])
      .addClasses(classOf[JaxrsActivator], classOf[CalcResource], classOf[CalcService])
      .addAsLibraries(
        Maven
          .resolver
          .loadPomFromFile("pom.xml")
          .importRuntimeDependencies
          .resolve("org.scalatest:scalatest_2.11:3.0.0")
          .withTransitivity
          .asFile: _*
      )

  @CreateSwarm
  def newContainer: Swarm = new Swarm
}

@RunWith(classOf[Arquillian])
@RunAsClient
class CalcResourceTest extends Matchers {
  private val resourcePrefix: String =
    classOf[JaxrsActivator]
      .getAnnotation(classOf[ApplicationPath])
      .value

  @ArquillianResource
  private var deploymentUrl: URL = _

  @Test
  def calcTest(): Unit = {
    val client = ClientBuilder.newBuilder.build

    try {
      val response =
        client
          .target(s"${deploymentUrl}${resourcePrefix}/calc/add?a=5&b=3")
          .request
          .get

      response.readEntity(classOf[String]) should be("8")

      response.close()
    } finally {
      client.close()
    }
  }
}

CDI管理Beanのテスト。
src/test/scala/org/littlewings/javaee7/microprofile/service/CalcServiceTest.scala

package org.littlewings.javaee7.microprofile.service

import javax.inject.Inject

import org.jboss.arquillian.container.test.api.Deployment
import org.jboss.arquillian.junit.Arquillian
import org.jboss.shrinkwrap.api.ShrinkWrap
import org.jboss.shrinkwrap.api.spec.WebArchive
import org.jboss.shrinkwrap.resolver.api.maven.Maven
import org.junit.Test
import org.junit.runner.RunWith
import org.scalatest.Matchers
import org.wildfly.swarm.Swarm
import org.wildfly.swarm.arquillian.CreateSwarm

object CalcServiceTest {
  @Deployment
  def createDeployment: WebArchive =
    ShrinkWrap
      .create(classOf[WebArchive])
      .addClass(classOf[CalcService])
      .addAsLibraries(
        Maven
          .resolver
          .loadPomFromFile("pom.xml")
          .importRuntimeDependencies
          .resolve("org.scalatest:scalatest_2.11:3.0.0")
          .withTransitivity
          .asFile: _*
      )

  @CreateSwarm
  def newContainer: Swarm = new Swarm
}

@RunWith(classOf[Arquillian])
class CalcServiceTest extends Matchers {
  @Inject
  var calcService: CalcService = _

  @Test
  def addTest(): Unit =
    calcService.add(5, 3) should be(8)
}

コードの書き方自体は通常のArquillianを使った場合と変わりませんが、WildFly Swarmのコンテナについての設定などを入れる場合は、@CreateSwarmを付与したstaticメソッドでコンテナを返すように実装するようです。

  @CreateSwarm
  def newContainer: Swarm = new Swarm

今回は特になにもしていないので、このメソッドは実はなくても動作します。

あとは、テストを実行するだけ。

$ mvn test

ちょっと重いのですが、これでArquillianを使ったテストが実行されます。

まとめ

WildFly Swarmを使って、ふつうのJava EEアプリケーションを動作させてみました。

このままだとIDEからmainメソッドで実行できないじゃないかとかありますが、一応やりたいことは実現できましたと。

IDEからmainメソッド欲しかったらさすがにコンテナ使ったコード書くかなぁとか、DataSource使いたかったらどうしようとかあるのですが…。

DataSourceとかは、どちらかというと設定ファイルにしたい派なんですよね。アプリケーションサーバー依存になりますけど、*-ds.xmlとか用意すればいいのかな。

あと、Arquillianを使ったテストはやっぱり重いので、UnitTestの時はApache DeltaSpikeでできる範囲は代用とかかなぁとちょっと思ったり。

まあ、個人の趣味程度に遊んでいきたいと思います。

今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/javaee7-scala-examples/tree/master/wildfly-swarm-mp-jaxrs-cdi