WildFly Swarmではじめる「パーツとしてのJavaEE」

天神LT勉強会 on ZusaarWildFly Swarmに関して話してきましたので、ブログにまとめ直してみます。

JavaEEコンテナの世界観

昔ながらのJavaEEコンテナはすべてがJavaEEで完結することを目指して作られていました。 なので、WeblogicGlassFish, WildFly等には下記のような機能があります。

あと、リソースを効率的に使うために、一つのマシンに複数のアプリを乗せる事を前提とした機能も多いです。

「同一のミドルなのでシステムはうまく結合していて、管理も簡単!」

JavaEEコンテナさえあればインフラは他には不要!」

というやさしい世界を目指してる感じなのですが、実際は

f:id:pascal256:20150708221439j:plain

「そんなのないよ、ありえない」

という感じです。

従来のJavaEEコンテナの限界

どうありえないかですが、だいたい下記のような問題があります。

  • ブルーグリーンデプロイメントとかを考えるとクラスタリング機能が弱い(URLが同じアプリを複数持てない)
  • OSを含めてJavaEE以外のシステムの監視もあるので、アラートはZabbixとか別でやりたい
  • 無停止デプロイ系は困ったちゃん(信頼するには精度が微妙で、理解するにはブラックボックス過ぎる)
  • そもそも、JavaEEだけでは完結しない(Apacheが必要だったり、ローカルファイルが必要だったり)

すべてがJavaEEで完結するやさしい世界なら他のミドルも要らないし、幸せなのですが、現実は過酷です。色々なツールを組み合わせて使ってることが多いでしょう。

当然、そうなると組合せを最適化するための支援ツールが必要になってくるのですが、それがDockerであったり、Consulであったり、あるいはOpenShiftやCloudFoundryのようなPaaSというのが現代の流れです。

個々のシステムを上記に挙げたような基盤でくっつけていく感じですね。この流れにJavaEEコンテナも統合したい。

別に、現状でも統合できるのですが、単なる実行以外の機能もたくさん付いていので、無駄に重かったり、デプロイしづらかったりと、扱いづらかったりするのが現状の課題です。

WildFly Swarm

そこでWildFly Swarmです。

これは乱暴に言えば、SpringBootのJavaEE版です。WildFlyコンポーネントと組合せて、実行可能なFat-JARを作成します。

クラスタ機能とかそういったものは一切なくなるので、Docker等とかと組み合わせて、クラスタリングやスケジューリング、デプロイなんかを考えるのが前提です。

内部的には同じくJBossプロジェクトで出しているJavaEEのテストツールであるArquillianでも使われているShrinkwrapを使ってFat-JARを作成しているようです。

WildFly 9 betaをベースとしていて、Swarm自身もAlpha3ということもあって、JSFなんかはまだ正常動作しませんが、CDIJAX-RSJPAなんかは動きます。

パーツとしてのJavaEE

WildFly Swarmを使うことで、DockerやConsulなどの複合システムでアプリケーション実行という機能を提供する「パーツ」として使いやすくなります。

結果として、JavaEEコンテナで完結するよりは複雑な構成になりますが、JavaEEとそれ以外で2重に色々なものが存在する状態よりはずっと楽になります。

似たようなアプローチとしては、GlassFishベースのPayara MicroとWebSphareのLiberty Profileがあります。

逆に、対極のアプローチが次期Weblogicに搭載される予定のマルチテナントです。同一のWeblogic複数アプリをより互いの影響を小さくした状態にして相乗りさせ、効率的に集約をする機能です。

従来のJavaEE的な考え方を追及する方向なので、そもそもJavaEEのベースになっているWeblogicとしては自然な進化の気もします。OS仮想化やDockerによるコンテナ化よりもずっと効率的でしょうし。

使い方

前置きが長くなってしまいましたが、使い方です。JAX-RS, CDI, JPA(h2database利用)を使ったシンプルなプログラムをGitHubにおいてあります

WildFlyのインストールやその他準備は不要で、Mavenがあれば動きます。NetBeansとかMavenと連携できるIDE使えば開発も楽ちんです。

まずは、特に工夫をせず、シンプルにJAX-RS, CDI, JPAを使ったコードをそれぞれ書きます。

リソースはEmployeeServiceのfindAllお読んで結果を返す感じ。

@ApplicationScoped
@Path("/employees")
public class EmployeeResource {

    @Inject
    private EmployeeService employeeService;

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<Employee> findAll() {
        List<Employee> results = employeeService.findAll();
        System.err.println(results);
        return results;
    }

}

サービスは単純にJPAを呼ぶだけ。

@ApplicationScoped
public class EmployeeService {

    @Inject
    EntityManager em;

    public List<Employee> findAll() {
        return em.createQuery("SELECT e FROM Employee e", Employee.class).getResultList();
    }

}

EntityはLombok使いながらシンプルなJPA

@Entity
@Data
@AllArgsConstructor
public class Employee implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    public Employee() {    }
}

ここまでは普通のJavaEEな書き方。

ここからが、Swarmの書き方。

まずはBootStrapになるクラスを作成。Arquillianと雰囲気にてますね。

public class Main {

    public static void main(String[] args) throws Exception {

        Container container = new Container();
        registDataSource(container);

        JAXRSDeployment deployment = new JAXRSDeployment(container);

        // set config
        deployment.getArchive().addClass(JpaResources.class);
        deployment.addResource(JaxRsApplication.class);

        // load resouces
        deployment.getArchive().addAsWebInfResource(new ClassLoaderAsset("META-INF/beans.xml", Main.class.getClassLoader()), "beans.xml");
        deployment.getArchive().addAsWebInfResource(new ClassLoaderAsset("META-INF/persistence.xml", Main.class.getClassLoader()), "classes/META-INF/persistence.xml");
        deployment.getArchive().addAsWebInfResource(new ClassLoaderAsset("META-INF/load.sql", Main.class.getClassLoader()), "classes/META-INF/load.sql");

        // load classes
        deployment.addResource(EmployeeResource.class);
        deployment.getArchive().addPackage("cn.orz.pascal.example.wilfly.swarm.domain");

        container.start().deploy(deployment);
    }

データベースの登録はこんな感じ。

    private static void registDataSource(Container container) {
        container.subsystem(new DatasourcesFraction()
                .driver(new Driver("h2")
                        .datasourceClassName("org.h2.Driver")
                        .xaDatasourceClassName("org.h2.jdbcx.JdbcDataSource")
                        .module("com.h2database.h2"))
                .datasource(new Datasource("InMemoryPersistenceUnit")
                        .driver("h2")
                        .connectionURL("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE")
                        .authentication("sa", "sa"))
        );

        // Prevent JPA Fraction from installing it's default datasource fraction
        container.fraction(new JPAFraction()
                .inhibitDefaultDatasource()
                .defaultDatasourceName("InMemoryPersistenceUnit")
        );
    }

つづいて、pom.xmlwildfly-swarm-pluginでMainクラスを指定するのがポイント。

  <groupId>cn.orz.pascal</groupId>
    <artifactId>example-wilfly-swarm</artifactId>
    <version>0.1</version>
    <packaging>jar</packaging>

    <name>example-wildfly-swarm</name>

    <properties>
        <version.wildfly-swarm>1.0.0.Alpha3</version.wildfly-swarm>

        <maven.min.version>3.2.1</maven.min.version>

        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.wildfly.swarm</groupId>
                <artifactId>wildfly-swarm-plugin</artifactId>
                <version>${version.wildfly-swarm}</version>
                <configuration>
                    <mainClass>cn.orz.pascal.example.wilfly.swarm.Main</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>package</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
  

ちなみに、NetBeans 8の組み込みMavenのバージョンは3.0.5なので実行すると下記のようなエラーがでます。 3.2以上のMavenを入れて指定しましょう。というか今時なんでこんなバージョンを...

WARNING: Error injecting: org.wildfly.swarm.plugin.PackageMojo
java.lang.NoClassDefFoundError: org/eclipse/aether/resolution/ArtifactResolutionException
    at java.lang.Class.getDeclaredConstructors0(Native Method)
    at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)

アプリケーションの実行は

$ mvn package

をすると${app-name}-swarm.jarができるので

$ java -jar ${app-name}=swarm.jar

で実行するだけです。IDEからなら単純にMainクラスを実行するだけですね。実行したら

http://localhost:8080/employees

にアクセス。中々いい感じに動きます。

まとめ

SpringBoot開発も運用も軽量で良いなー、とJavaEE派としては羨ましがってたんですが、これはなかなか良さそうです。

Dockerとの組み合わせも簡単になりますしね。とはいえ、アルファ版なので完成度とかは今後に期待。Payara Microの方が現時点の完成度は高そうだし、KVSとの連携もできるみたいだから、そっちも確認して見ようかな?

しかし、JavaEE8ではマルチテナントなんかより、こっちの機能を標準化して欲しい...

参考