CLOVER🍀

That was when it all began.

WildFly Bootable JARを詊す

これは、なにをしたくお曞いたもの

WildFly Bootable JARずいうものが䟿利そうだったので、ちょっず詊しおみるこずにしたした。

WildFly Bootable JARずは

WildFly Bootable JARずはWildFly Bootable JAR Maven Pluginを䜿っお䜜成する、実行可胜JARファむルWildFly Bootable JARを
䜜る仕組みです。

GitHub - wildfly-extras/wildfly-jar-maven-plugin: WildFly Bootable JAR

WildFly Bootable JAR is final!

単玔化しお蚀うず、WARなどのアプリケヌションにWildFlyを远加しお、WildFlyを含めお実行可胜なJARファむルにする
仕組みのようです。

Spring Bootみたいですね。

WildFly Bootable JAR Maven Plugiinを䜿っお䜜成したWildFly Bootable JARは、通垞のWildFlyず同様に動䜜するそうです。
WildFly CLIを䜿っお操䜜できるこずもできるずか。

制限事項は、以䞋だそうです。

  • シャットダりン䞭のサヌバヌの再起動は䞍可
  • 実行䞭の倉曎WildFly CLIでの曎新は保持されず、サヌバヌが終了するずその倉曎も倱われる
  • サヌバヌを管理者モヌドで起動するこずはできない

ドキュメントは、こちらです。

WildFly Bootable JAR Documentation

WildFly偎のドキュメントにも、蚘茉がありたす。

Bootable JAR Guide

いずれも、WildFlyのドキュメントのトップからたどれたすね。

WildFly Documentation

登堎したのは、WildFly 20からのようです。

A bootable JAR for WildFly 20

ずころで、こう曞くずQuarkusがあるのではずも思ったりするのですが、Quarkusは盎接はWildFlyの代わりにはならないので
あくたでJakarta EEを䜿甚したアプリケヌションを実行可胜JARずしたい堎合は、こちらの仕組みを䜿うのがよいのでしょうね。

できるこずを、もう少し

WildFly Bootable JAR Maven Pluginを䜿っおできるこずを、もう少し曞いおみたしょう。

テストでの利甚に関しおは、Arquillianを䜿うようです。

https://github.com/wildfly/wildfly-arquillian/tree/3.0.1.Final/container-bootable

各皮サンプルは、こちら。

https://github.com/wildfly-extras/wildfly-jar-maven-plugin/tree/5.0.1.Final/examples

ずいうわけで、たずは䜿っおみたしょう。

環境

今回の環境は、こちらです。

$ java --version
openjdk 11.0.11 2021-04-20
OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.20.04)
OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.20.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.1 (05c21c65bdfed0f71a2f2ada8b84da59348c4c5d)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.11, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-77-generic", arch: "amd64", family: "unix"

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

たずは、動䜜確認するためのサンプルアプリケヌションを䜜成したす。

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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>wildfly-bootable-jar-example</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>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>jakarta.platform</groupId>
                <artifactId>jakarta.jakartaee-bom</artifactId>
                <version>8.0.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>jakarta.ws.rs</groupId>
            <artifactId>jakarta.ws.rs-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.enterprise</groupId>
            <artifactId>jakarta.enterprise.cdi-api</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

簡単な、JAX-RSCDIを䜿ったWebアプリケヌションを甚意したしょう。

src/main/java/org/littlewings/jakarta/wildfly/bootable/JaxrsActivator.java

package org.littlewings.jakarta.wildfly.bootable;

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

@ApplicationPath("/")
public class JaxrsActivator extends Application {
}

src/main/java/org/littlewings/jakarta/wildfly/bootable/MessageResource.java

package org.littlewings.jakarta.wildfly.bootable;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

@ApplicationScoped
@Path("message")
public class MessageResource {
    @Inject
    MessageService messageService;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello(@QueryParam("word") @DefaultValue("World") String word) {
        return messageService.message(word);
    }
}

src/main/java/org/littlewings/jakarta/wildfly/bootable/MessageService.java

package org.littlewings.jakarta.wildfly.bootable;

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class MessageService {
    public String message(String word) {
        return String.format("Hello %s!!", word);
    }
}

パッケヌゞングするず、WARファむルができたす。

$ mvn package

$ ll -h target/wildfly-bootable-jar-example-0.0.1-SNAPSHOT.war
-rw-rw-r-- 1 xxxxx xxxxx 4.9K  7月  3 19:01 target/wildfly-bootable-jar-example-0.0.1-SNAPSHOT.war

たあ、ふ぀うです。

WildFly Bootable JAR Maven Pluginを远加する

このサンプルアプリケヌションに、WildFly Bootable JAR Maven Pluginを远加しおパッケヌゞングしおみたしょう。

シンプルには、こんな感じですね。

Adding the bootable JAR Maven plugin to your pom file

    <build>
        <plugins>
            <plugin>
                <groupId>org.wildfly.plugins</groupId>
                <artifactId>wildfly-jar-maven-plugin</artifactId>
                <version>5.0.1.Final</version>
                <configuration>
                    <feature-pack-location>wildfly@maven(org.jboss.universe:community-universe)#24.0.0.Final</feature-pack-location>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>package</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

feature-pack-locationで、䜿甚するWildFlyのバヌゞョンを指定したす。今回は、24.0.0.Finalです。

Specifying the WildFly server version to use

                <configuration>
                    <feature-pack-location>wildfly@maven(org.jboss.universe:community-universe)#24.0.0.Final</feature-pack-location>
                </configuration>

バヌゞョンを指定しない堎合は、最新のWildFlyが䜿われたす。

こちらは、mvn packageで動䜜させるためのものです。

                <executions>
                    <execution>
                        <goals>
                            <goal>package</goal>
                        </goals>
                    </execution>
                </executions>

この状態でパッケヌゞングするず

$ mvn package

WildFly Bootable JAR Maven Pluginが動䜜し

[INFO] --- wildfly-jar-maven-plugin:5.0.1.Final:package (default) @ wildfly-bootable-jar-example ---
[INFO] Provisioning server configuration based on the standalone-microprofile.xml default configuration
[INFO] Building server based on [[wildfly@maven(org.jboss.universe:community-universe)#24.0.0.Final inherit-packages=false inheritConfigs=false includedConfigs [model=standalone name=standalone-microprofile.xml]]] galleon feature-packs
[INFO] Found boot artifact org.wildfly.core:wildfly-jar-boot:jar:16.0.0.Final:provided in org.wildfly:wildfly-ee-galleon-pack:24.0.0.Final
7月 03, 2021 7:06:42 午埌 org.wildfly.core.embedded.LoggerContext$JBossLoggingModuleLogger greeting
INFO: JBoss Modules version 1.11.0.Final
7月 03, 2021 7:06:42 午埌 org.jboss.msc.service.ServiceContainerImpl <clinit>
INFO: JBoss MSC version 1.4.12.Final
7月 03, 2021 7:06:42 午埌 org.jboss.threads.Version <clinit>
INFO: JBoss Threads version 2.3.2.Final
7月 03, 2021 7:06:42 午埌 org.jboss.as.server.ApplicationServerService start
INFO: WFLYSRV0049: WildFly Full 24.0.0.Final (WildFly Core 16.0.0.Final) starting
7月 03, 2021 7:06:43 午埌 org.jboss.as.patching.installation.InstallationManagerService start
INFO: WFLYPAT0050: WildFly Full cumulative patch ID is: base, one-off patches include: none
7月 03, 2021 7:06:43 午埌 org.jboss.as.server.suspend.SuspendController resume
INFO: WFLYSRV0212: Resuming server
7月 03, 2021 7:06:43 午埌 org.jboss.as.server.BootstrapListener done
INFO: WFLYSRV0025: WildFly Full 24.0.0.Final (WildFly Core 16.0.0.Final) started in 1091ms - Started 29 of 32 services (3 services are lazy, passive or on-demand)
7月 03, 2021 7:06:43 午埌 org.wildfly.security.Version <clinit>
INFO: ELY00001: WildFly Elytron version 1.16.0.Final
7月 03, 2021 7:06:45 午埌 org.jboss.as.controller.AttributeDefinition validateAndSet
INFO: WFLYCTL0028: Attribute 'security-realm' in the resource at address '/subsystem=undertow/server=default-server/https-listener=https' is deprecated, and may be removed in a future version. See the attribute description in the output of the read-resource-description operation to learn more about the deprecation.
7月 03, 2021 7:06:45 午埌 org.jboss.as.server.ApplicationServerService stop
INFO: WFLYSRV0050: WildFly Full 24.0.0.Final (WildFly Core 16.0.0.Final) stopped in 10ms
[INFO] Executing CLI, Server configuration
[INFO] CLI scripts execution done.

WARファむル以倖に、-bootable.jarず付いたJARファむルが䜜成されたす。125Mありたすけど。

$ ll -h target/wildfly-bootable-jar-example-0.0.1-SNAPSHOT*.*
-rw-rw-r-- 1 xxxxx xxxxx 125M  7月  3 19:07 target/wildfly-bootable-jar-example-0.0.1-SNAPSHOT-bootable.jar
-rw-rw-r-- 1 xxxxx xxxxx 4.9K  7月  3 19:06 target/wildfly-bootable-jar-example-0.0.1-SNAPSHOT.war

このJARファむルを実行するず

$ java -jar target/wildfly-bootable-jar-example-0.0.1-SNAPSHOT-bootable.jar

文字通りWildFlyが起動したす。

19:08:35,107 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: WildFly Full 24.0.0.Final (WildFly Core 16.0.0.Final) started in 4582ms - Started 318 of 463 services (221 services are lazy, passive or on-demand

WARファむルは、ROOT.warずしおデプロむされたす。

19:08:35,068 INFO  [org.jboss.as.server] (Controller Boot Thread) WFLYSRV0010: Deployed "wildfly-bootable-jar-example-0.0.1-SNAPSHOT.war" (runtime-name : "ROOT.war")

このため、コンテキストパスは/でアクセスできたす。

$ curl localhost:8080/message
Hello World!!


$ curl localhost:8080/message?word=WildFly
Hello WildFly!!

動䜜確認もできたしたね。

Bootable JAR実行時に䜿える匕数は、こちら。

Bootable JAR arguments

起動は、wildfly-jar:runやwildfly-jar:startでも行うこずができたす。

$ mvn package wildfly-jar:run

wildfly-jar:runずwildfly-jar:startの違いは、バックグラりンドで動䜜するかどうかずなりたす。

wildfly-jar:startで起動した堎合は、wildfly-jar:shutdownで停止する必芁がありたす。

$ mvn wildfly-jar:shutdown

コンテキストパスを倉曎する堎合は、contextRootずいうWildFly Bootable JAR Maven Pluginの蚭定で行うようです。

URL context path of deployed application

executionを蚭定しない堎合は

以䞋のexecutionの指定を入れない堎合、

                <executions>
                    <execution>
                        <goals>
                            <goal>package</goal>
                        </goals>
                    </execution>
                </executions>

Bootable JARを䜜成するためのコマンドはこうなりたす。

$ mvn package wildfly-jar:package

先にpackageゎヌルを指定しおおくこずが必須ずなり、wildfly-jar:package単䜓では動䜜したせん。

あくたで、デプロむ察象を先に䜜っおおくこずがポむントになりたす。Hollow JARの堎合は埮劙ですが。

wildfly-jar:runをこの前提で曞こうずするず、けっこう長くなりたす 。

$ mvn package wildfly-jar:package wildfly-jar:run

蚭定を行う

Bootable JARずしお構成されるWildFlyの蚭定を倉曎する方法はいく぀かありたすが、基本はパッケヌゞング時でしょうね。

WildFly CLIのスクリプトを䜿っお蚭定するこずになりたす。

Configuring the server during packaging

今回は、扱いたせん 。

コンポヌネントをカスタマむズする

次に、利甚するコンポヌネントをカスタマむズしおみたしょう。

Galleon configuration

Composing custom server with Galleon layers

ここで出おくるGalleonずいうのは、耇数のコンポヌネントで構成される゜フトりェアを䜜成、プロビゞョニングするための
ツヌルです。

Galleon Documentation

芁するに、WildFly Bootable JAR Maven Pluginでは、このGalleonを䜿っお利甚するコンポヌネントを遞択するこずになりたす。

デフォルトでは、standalone-microprofile.xml盞圓の構成でBootable JARが䜜成されるようです。

Specifying the set of Galleon layers to use

こちらでしょうかね

https://github.com/wildfly/wildfly/blob/24.0.0.Final/galleon-pack/galleon-content/src/main/resources/configs/standalone/standalone-microprofile.xml/config.xml

コンポヌネントの蚭定を行うには、2぀の曞き方があるようです。

ずはいえ、互いに排他的な関係ではなさそうですが。

ただ、feature-packを䜿った堎合はfeature-pack-locationを䜿ったWildFlyのバヌゞョン指定はできなくなり、
これもfeature-packずしお指定するこずになりたす。

<feature-packs>
    <feature-pack>
        <location>wildfly@maven(org.jboss.universe:community-universe)#24.0.0.Final</location>
    </feature-pack>
    <feature-pack>
        <groupId>org.wildfly</groupId>
        <artifactId>wildfly-datasources-galleon-pack</artifactId>
        <version>1.0.6.Final</version>
    </feature-pack>
</feature-packs>

デヌタ゜ヌスを扱う堎合などは、feature-packを䜿うこずになりたす。

今回は、Galleon layerのみを䜿いたす。

Galleon layerにも2皮類あり、基本なlayerBasic layerず、その組み合わせで衚珟されるFoundational layerがありたす。

この2぀のリストず、先ほどのstandalone-microprofile.xmlに曞かれおいるlayersの内容を芋るず、どのようなlayerが
遞択されおいるのかがわかるでしょう。

    <layers>
        <include name="cloud-server"/>
        <include name="h2-default-datasource"/>
        <include name="microprofile-fault-tolerance"/>
        <include name="microprofile-health"/>
        <include name="microprofile-jwt"/>
        <include name="microprofile-metrics"/>
        <include name="microprofile-openapi"/>
        <include name="microprofile-opentracing"/>
        <include name="microprofile-rest-client"/>
        <include name="undertow-legacy-https"/>
        <exclude name="management-security-realm"/>
    </layers>

https://github.com/wildfly/wildfly/blob/24.0.0.Final/galleon-pack/galleon-content/src/main/resources/configs/standalone/standalone-microprofile.xml/config.xml

たた、layerは陀倖するこずもできたす。

Excluding Galleon layers

今回は、jaxrs-serverずmanagementを入れおおきたしょう。

                <configuration>
                    <feature-pack-location>wildfly@maven(org.jboss.universe:community-universe)#24.0.0.Final</feature-pack-location>
                    <layers>
                        <layer>jaxrs-server</layer>
                        <layer>management</layer>
                    </layers>
                </configuration>

パッケヌゞングするず
※今回はexecutionの蚭定は倖しおいたす

$ mvn package wildfly-jar:package

最初よりも、10MBほど小さくなりたした。

$ ll -h target/wildfly-bootable-jar-example-0.0.1-SNAPSHOT*.*
-rw-rw-r-- 1 xxxxx xxxxx 114M  7月  3 20:02 target/wildfly-bootable-jar-example-0.0.1-SNAPSHOT-bootable.jar
-rw-rw-r-- 1 xxxxx xxxxx 4.9K  7月  3 20:01 target/wildfly-bootable-jar-example-0.0.1-SNAPSHOT.war

jaxrs-serverはFoundational layerであり、もう少し削れたす。

Basic layerである、jaxrsずcdiにしおみたしょう。

                <configuration>
                    <feature-pack-location>wildfly@maven(org.jboss.universe:community-universe)#24.0.0.Final</feature-pack-location>
                    <layers>
                        <layer>jaxrs</layer>
                        <layer>cdi</layer>
                        <layer>management</layer>
                    </layers>
                </configuration>

䞀気に半分くらいになりたした。

$ ll -h target/wildfly-bootable-jar-example-0.0.1-SNAPSHOT*.*
-rw-rw-r-- 1 xxxxx xxxxx  64M  7月  3 20:06 target/wildfly-bootable-jar-example-0.0.1-SNAPSHOT-bootable.jar
-rw-rw-r-- 1 xxxxx xxxxx 4.9K  7月  3 20:06 target/wildfly-bootable-jar-example-0.0.1-SNAPSHOT.war

こうやっお、必芁なコンポヌネントを遞んで、カスタマむズしおいくこずができたす。

Basic layerには䟝存もあり、このあたりは自動的に解決しおくれたす。

ちなみに、management layerはリモヌトアクセスでの管理機胜を提䟛するもので、WildFly Bootable JAR Maven Pluginでは
wildfly-jar:shutdownで䜿われるので、入れおおいた方が良い気がしたす。

開発で䜿う

蚭定は、以䞋に1床戻したす。

                <configuration>
                    <feature-pack-location>wildfly@maven(org.jboss.universe:community-universe)#24.0.0.Final</feature-pack-location>
                    <layers>
                        <layer>jaxrs-server</layer>
                        <layer>management</layer>
                    </layers>
                </configuration>

ここからは、開発甚途での䜿い方を曞いおいきたしょう。

JARファむルをスリムにする

ここたで芋おきた通り、Bootable JARはそれなりのサむズになりたす。

実環境では䜿えなくなるずは思いたすが、WildFlyのモゞュヌルをロヌカルのMavenリポゞトリから䜿甚するこずで、
JARファむルのサむズを小さくするこずができたす。

Provisioning a slim bootable JAR

plugin-optionsずしお、jboss-maven-distを远加したす。

                    <plugin-options>
                        <jboss-maven-dist/>
                    </plugin-options>

configurationずしおは、こうなりたす。

                <configuration>
                    <feature-pack-location>wildfly@maven(org.jboss.universe:community-universe)#24.0.0.Final</feature-pack-location>
                    <layers>
                        <layer>jaxrs-server</layer>
                        <layer>management</layer>
                    </layers>
                    <plugin-options>
                        <jboss-maven-dist/>
                    </plugin-options>
                </configuration>

これでパッケヌゞングするず

$ mvn package wildfly-jar:package

Bootable JARファむルのサむズが、䞀気に数Mたで小さくなりたす。

$ ll -h target/wildfly-bootable-jar-example-0.0.1-SNAPSHOT*.*
-rw-rw-r-- 1 xxxxx xxxxx 3.9M  7月  3 20:16 target/wildfly-bootable-jar-example-0.0.1-SNAPSHOT-bootable.jar
-rw-rw-r-- 1 xxxxx xxxxx 4.9K  7月  3 20:16 target/wildfly-bootable-jar-example-0.0.1-SNAPSHOT.war

パッケヌゞングなどにかかる時間も、短くなりたす。

゜ヌスコヌドの監芖を䜿った開発モヌド

次は、WildFly Bootable JAR Maven Pluginでの、開発䞭に䟿利なモヌドを䜿っおいきたしょう。

たずは、゜ヌスコヌドの監芖を䜿った開発モヌドです。

Development mode with source watching

䜿い方は簡単で、以䞋のコマンドを実行したす。

$ mvn wildfly-jar:dev-watch

するず、空のサヌバヌHollow JARが起動したす。

[INFO] Hollow jar, No application deployment added to server.

あずは、゜ヌスコヌドの倉曎を怜知するず自動で再コンパむルデプロむを行い、倉曎をサヌバヌに反映しおくれたす。

targetディレクトリが空の状態で起動しおも、勝手にパッケヌゞングデプロむしたす。

$ curl localhost:8080/message
Hello World!!

起動埌に゜ヌスコヌドを倉曎するず

@ApplicationScoped
public class MessageService {
    public String message(String word) {
        return String.format("Hello %s!??", word);
    }
}

倉曎を怜出しお再デプロむが行われたす。

[INFO] Changes detected - recompiling the module!
[INFO] Compiling 3 source files to /path/to/target/classes
[INFO] Exploding webapp
[INFO] Assembling webapp [wildfly-bootable-jar-example] in [/path/to/target/deployments/ROOT.war]
[INFO] Processing war project
[INFO] Webapp assembled in [5 msecs]
20:26:16,524 INFO  [org.wildfly.extension.undertow] (ServerService Thread Pool -- 9) WFLYUT0022: Unregistered web context: '/' from server 'default-server'
20:26:16,580 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-3) WFLYSRV0028: Stopped deployment ROOT.war (runtime-name: ROOT.war) in 62ms
20:26:16,607 INFO  [org.jboss.as.server] (management-handler-thread - 1) WFLYSRV0009: Undeployed "ROOT.war" (runtime-name: "ROOT.war")
20:26:16,615 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-2) WFLYSRV0027: Starting deployment of "ROOT.war" (runtime-name: "ROOT.war")
20:26:16,699 INFO  [org.jboss.weld.deployer] (MSC service thread 1-8) WFLYWELD0003: Processing weld deployment ROOT.war
20:26:17,025 INFO  [org.jboss.resteasy.resteasy_jaxrs.i18n] (ServerService Thread Pool -- 47) RESTEASY002225: Deploying javax.ws.rs.core.Application: class org.littlewings.jakarta.wildfly.bootable.JaxrsActivator
20:26:17,033 INFO  [org.wildfly.extension.undertow] (ServerService Thread Pool -- 47) WFLYUT0021: Registered web context: '/' for server 'default-server'
20:26:17,049 INFO  [org.jboss.as.server] (management-handler-thread - 1) WFLYSRV0010: Deployed "ROOT.war" (runtime-name : "ROOT.war")

確認。

$ curl localhost:8080/message
Hello World!??

なかなか䟿利ではないでしょうか

いく぀かポむント、泚意事項がありたす。

  • サヌバヌはフォアグラりンドで起動するので、停止はCtrl-cで行う
  • WAR、JAR、EARをサポヌト
  • src/main/javaデフォルトおよびsrc/main/webapp、src/main/resourcesの倉曎を怜知しお、再デプロむする
  • pom.xmlのWildFly Bootable JAR Maven Pluginの蚭定倉曎は、サヌバヌの再起動が必芁
  • pom.xmlのWildFly Bootable JAR Maven Plugin以倖の蚭定倉曎は、再デプロむが行われる
  • コンパむル゚ラヌでは停止しない
  • マルチモゞュヌルはサポヌトしおいない
  • リ゜ヌスファむルのフィルタリングincludeexcludeはサポヌトされおおらず、すべお監芖察象ずなる
開発モヌドサヌバヌ再パッケヌゞング

もうひず぀は、先に開発モヌドのサヌバヌを起動しおおき、パッケヌゞングするず自動で再デプロむする方法です。

Development mode with repackaging

最初に、wildfly-jar:devで開発サヌバヌを起動したす。

$ mvn wildfly-jar:dev

こちらもHollow JARです。

[INFO] Hollow jar, No application deployment added to server.

このサヌバヌはバックグラりンドで起動したす。

この状態で-Ddevを指定しおパッケヌゞングを行うず、target/deploymentsにファむルが配眮、デプロむが行われたす。

$ mvn package wildfly-jar:package -Ddev

あずは、゜ヌスコヌドを倉曎しおから同じように再パッケヌゞングするず、自動でデプロむされたす。

$ mvn package wildfly-jar:package -Ddev

wildfly-jar:dev-watchずの違いは、明瀺的な再パッケヌゞングがデプロむのトリガヌになるこずですね。

なお、開発サヌバヌはバックグラりンドで起動したたたなので、wildfly-jar:shutdownで停止したす。

$ mvn wildfly-jar:shutdown

この時に、management layerが必芁になりたす。

たずめ

WildFly Bootable JAR Maven Pluginを詊しおみたした。けっこう䟿利ではないでしょうか

実際に動かす環境ではWildFlyサヌバヌにデプロむするようなケヌスでも、手元では開発モヌドでBootable JARで動かしお確認しお、
実際の環境ではデプロむ時はWARファむルで、ずいう䜿い方も良いのかなず思いたす。

芚えおおきたしょう。

Infinispan 12.1でのMarshallingEncodingず分散凊理ず

これは、なにをしたくお曞いたもの

Infinispan 10.0で、デフォルトのMarshallingの仕組みがProtoStreamProtocol Buffersになりたした。

このバヌゞョン付近からHot Rodを前面に出す雰囲気になっおいたので、あたりEmbedded Modeは扱っおこなかったのですが。

そういえば、Protocol BuffersをMarshallingの仕組みに䜿い぀぀、分散凊理を行った堎合はMarshallingはどういった扱いに
なるのかなず思っお詊しおみるこずにしたした。

結果を芋るず、頑匵ればすべおをProtoStreamでのMarshallingにたずめるこずはできそうな気はしたす。
ただ、扱うクラスや構成にけっこう匕っ匵られそうな気も 。

今回は、あくたでEmbedded Modeでの話ですRemote Cacheの堎合は、クラむアント偎のMarshallerも関䞎するので。

Marshalling and Encoding Data

気づいおいなかったのですが、い぀の間にかMarshallingたわりのドキュメントが独立しおいたした。

Marshalling and Encoding Data

こちらのドキュメントに、Cacheぞ栌玍する際の゚ンコヌディング぀いお曞かれおいたす。

Cache Encoding

゚ンコヌディングずはメディアタむプによっお識別されるものであり、InfinispanがどのようにCacheに゚ントリキヌ、倀を
栌玍するかに圱響したす。

Marshalling and Encoding Data / Cache encoding

Remote Cacheの堎合は、このようになりたす。

  • Infinispan Serverは、゚ントリをCacheに蚭定された゚ンコヌディングで保存する
  • Hot RodやREST Clientにはリク゚ストにメディアタむプが含たれおおり、Cacheの蚭定ず異なるメディアタむプが指定された堎合、Infinispan Serverはオンデマンドで倉換を行う
    • Cacheに゚ンコヌディング蚭定がない堎合は、Infinispan Serverはbyte[]で゚ントリを保存する
      • Clinetからのデヌタ倉換芁求によっおは、予期しない結果になるこずもある
  • 耇数の皮類のClinetHot Rod、REST、Infinispan Console、CLIなどを䜿甚する堎合は、ProtoStream゚ンコヌディングを掚奚

Embedded Cacheの堎合は、このようになりたす。

  • クラスタ化されたCacheの堎合、byte[]にマヌシャリングする必芁がある
    • ロヌカルモヌドの堎合は、POJOずしお保存する
  • 特にMarshallerを構成しない堎合は、ProtoStream Marshallerでマヌシャリングされる

Infinispanでは以䞋の゚ンコヌディングを䜿うこずができ、互換性のある゚ンコヌディングの間で倉換を行うこずができたす。

゚ンコヌディング メディアタむプ 倉換可胜なメディアタむプ 備考
ProtoStream / Protobuf encoding application/x-protostream application/json,  Hot Rod、REST、Infinspan Consoleで盞互運甚可胜
Text-based cache encoding text/plain application/xml

application/json

application/x-protostream

application/x-jboss-marshalling

application/x-java-serialized
JBoss Marshalling application/x-jboss-marshalling  珟圚は非掚奚。シリアラむズ可胜なリストを蚭定する必芁あり
Java Serialization application/x-java-serialized-object  ProtoStreamよりパフォヌマンスに劣る。シリアラむズ可胜なリストを蚭定する必芁あり

 の郚分は、ドキュメント䞊でハッキリずした明瀺はありたせんが、テキストベヌスのメディアタむプずは倉換可胜
ず芋およいんでしょうね。

REST Clinetなどからの利甚もありたすし。

Client偎でのMarshalling

Client偎でのMarshallingに぀いおは、各メディアタむプやテヌマごずにドキュメントが曞かれおいたす。

Text-based cache encoding / Clients and text-based encoding

Marshalled Java objects / Clients and marshalled objects

Plain Old Java Objects (POJO) / Clients and POJOs

Hot Rod Clientの堎合は、ProtoStreamやJava Serialization、テキストベヌスのMarshallerを䜿甚したす。

REST Clientの堎合は、JSONやXML、テキストを䜿甚したす。

デヌタ倉換

さらに、Cacheから゚ントリを取埗する際に、異なるメディアタむプに倉換するこずも可胜なようです。

Data conversion

今回は、ここは芋たせん。

分散凊理では

ずころで、Infinispanずいえば分散凊理もあるでしょう。このあたりでしょうか。

このあたりの機胜に぀いお曞かれおいるずころを芋るず、Marshallingに぀いおは基本的にSerializableを想定しお
曞かれおいたす。

Cluster Executorはドキュメント䞊は蚘茉がありたせんが、こちらもSerializableが想定されおいたす。

ClusterExecutor (Infinispan JavaDoc 12.1.11.Final API)

よっお、ProtoStreamProtocol Buffersずはたた別ですね。

Cacheに栌玍する゚ントリはProtoStreamでMarshallingする堎合、このあたりのずの関係はどうなるのでしょうか

ずいうこずで詊しおみたす。

お題

以䞋のお題で、詊しおみたいず思いたす。

  • Cacheに栌玍する倀には自分でクラスを甚意し、ProtoStreamでMarshallingする
    • ネタは曞籍で
  • Distributed StreamおよびFunctional Mapを䜿っお、Nodeをたたぐ凊理を䜿う
    • ここで受け枡す凊理の結果を衚すクラスも、自分でクラスを甚意する

この時に、Marshallingをどうしたらいいかずいうのを詊しおいきたす。

環境

今回の環境は、こちら。

$ java --version
openjdk 11.0.11 2021-04-20
OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.20.04)
OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.20.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.1 (05c21c65bdfed0f71a2f2ada8b84da59348c4c5d)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.11, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-77-generic", arch: "amd64", family: "unix"

準備

Maven䟝存関係などは、こちら。

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

    <dependencies>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-core</artifactId>
            <version>12.1.4.Final</version>
        </dependency>

        <dependency>
            <groupId>org.infinispan.protostream</groupId>
            <artifactId>protostream-processor</artifactId>
            <version>4.4.1.Final</version>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.7.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.7.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.20.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.31</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
        </plugins>
    </build>

Infinispanは12.1.4.Finalを䜿い、動䜜確認はテストコヌドで行いたす。

ログラむブラリはSLF4JLogbackずし、なにも蚭定しない堎合のj.u.l.Loggerよりは芋やすいログにしおおきたす。

src/test/resources/logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

Cacheに栌玍する倀

Cacheに栌玍するのは、曞籍をお題にこちらのクラスずしたす。

src/main/java/org/littlewings/infinispan/distexec/protostream/entity/ProtoBook.java

package org.littlewings.infinispan.distexec.protostream.entity;

import org.infinispan.protostream.annotations.ProtoFactory;
import org.infinispan.protostream.annotations.ProtoField;

public class ProtoBook {
    @ProtoField(number = 1)
    String isbn;

    @ProtoField(number = 2)
    String title;

    @ProtoField(number = 3, defaultValue = "0")
    int price;

    @ProtoFactory
    public static ProtoBook create(String isbn, String title, int price) {
        ProtoBook book = new ProtoBook();

        book.setIsbn(isbn);
        book.setTitle(title);
        book.setPrice(price);

        return book;
    }

    // gettersetterは省略
}

キヌは、Stringを䜿うこずにしたしょう。぀たり、Cache<String, ProtoBook>です。

自分で定矩するクラスをProtoStreamでMarshallingする堎合は、こちらのドキュメントに埓っおアノテヌションを付䞎したり
したす。

Marshalling custom objects with ProtoStream

SerializationContextInitializerの䜜成も必芁です。

src/main/java/org/littlewings/infinispan/distexec/protostream/entity/EntityInitializer.java

package org.littlewings.infinispan.distexec.protostream.entity;

import org.infinispan.protostream.SerializationContextInitializer;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;

@AutoProtoSchemaBuilder(
        includeClasses = ProtoBook.class,
        schemaFileName = "entities.proto",
        schemaFilePath = "proto",
        schemaPackageName = "entity"
)
public interface EntityInitializer extends SerializationContextInitializer {
}

今回は、このProtoBookをCacheに栌玍し、priceフィヌルドを扱う分散凊理を曞くこずにしたしょう。

テストコヌドの雛圢

では、たずはテストコヌドの雛圢を䜜成したす。

src/test/java/org/littlewings/infinispan/distexec/protostream/ProtoStreamDistExecTest.java

package org.littlewings.infinispan.distexec.protostream;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.infinispan.Cache;
import org.infinispan.distribution.DistributionManager;
import org.infinispan.distribution.LocalizedCacheTopology;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.jboss.logging.Logger;
import org.junit.jupiter.api.Test;
import org.littlewings.infinispan.distexec.protostream.entity.ProtoBook;
import org.littlewings.infinispan.distexec.protostream.entity.ProtoSummary;
import org.littlewings.infinispan.distexec.protostream.entity.SerializableSummary;
import org.littlewings.infinispan.distexec.protostream.entity.Summary;

import static org.assertj.core.api.Assertions.assertThat;

public class ProtoStreamDistExecTest {
    Logger logger = Logger.getLogger(ProtoStreamDistExecTest.class);

    List<ProtoBook> books =
            List.of(
                    ProtoBook.create("978-1782169970", "Infinispan Data Grid Platform Definitive Guide", 5344),
                    ProtoBook.create("978-1849518222", "Infinispan Data Grid Platform", 3608),
                    ProtoBook.create("978-0359439379", "The Apache Ignite Book", 7686),
                    ProtoBook.create("978-1365732355", "High Performance in-memory computing with Apache Ignite", 6342),
                    ProtoBook.create("978-1789347531", "Apache Ignite Quick Start Guide: Distributed data caching and processing made easy", 3638),
                    ProtoBook.create("978-1785285332", "Getting Started with Hazelcast - Second Edition: Get acquainted with the highly scalable data grid, Hazelcast, and learn how to bring its powerful in-memory features into your application", 4209),
                    ProtoBook.create("978-1617295522", "Spark in Action, Second Edition: Covers Apache Spark 3 with Examples in Java, Python, and Scala", 6297),
                    ProtoBook.create("978-1484257807", "Beginning Apache Spark Using Azure Databricks: Unleashing Large Cluster Analytics in the Cloud", 4817),
                    ProtoBook.create("978-1788997829", "Apache Kafka Quick Start Guide: Leverage Apache Kafka 2.0 to simplify real-time data processing for distributed applications", 3516),
                    ProtoBook.create("978-1491936160", "Kafka: The Definitive Guide: Real-Time Data and Stream Processing at Scale", 4989)
            );

    <K, V> void withCache(String cacheName, int numInstances, Consumer<Cache<K, V>> func) {
        List<EmbeddedCacheManager> managers =
                IntStream
                        .rangeClosed(1, numInstances)
                        .mapToObj(i -> {
                            try {
                                return new DefaultCacheManager("infinispan.xml");
                            } catch (IOException e) {
                                throw new UncheckedIOException(e);
                            }
                        })
                        .collect(Collectors.toList());

        managers.forEach(manager -> manager.getCache(cacheName));

        try {
            Cache<K, V> cache = managers.get(0).getCache(cacheName);

            func.accept(cache);
        } finally {
            managers.forEach(manager -> manager.stop());
        }
    }

    // ここに、テストを曞く
}

簡単にクラスタを構成できるメ゜ッド付き。

infinispan.xmlの内容は、たた埌で曞きたす。

こんなメ゜ッドで動䜜確認。ここで、bookCacheずいうのはownersが1のDistributed Cacheずしたす。
たた、Nodeは3぀にしおクラスタを構成しおいたす。以降に出おくるテストコヌドも党郚3 Nodeにしたす。

    @Test
    public void simple() {
        this.<String, ProtoBook>withCache("bookCache", 3, cache -> {
            books.forEach(book -> cache.put(book.getIsbn(), book));

            assertThat(cache).hasSize(books.size());

            ProtoBook infinispanBook = cache.get("978-1782169970");
            assertThat(infinispanBook.getTitle()).isEqualTo("Infinispan Data Grid Platform Definitive Guide");
            assertThat(infinispanBook.getPrice()).isEqualTo(5344);

            DistributionManager dm = cache.getAdvancedCache().getDistributionManager();
            cache.keySet().forEach(isbn -> {
                LocalizedCacheTopology cacheTopology = dm.getCacheTopology();
                logger.infof("isbn = %s, members = %s", isbn, cacheTopology.getWriteOwners(isbn));
            });
        });
    }

ログ出力しおいるので、このコヌドを動䜜させるず、各キヌがどのNodeに配眮されおいるか確認できるでしょう。

Distributed Streamを䜿った凊理を曞く

では、Distributed Streamを䜿った凊理を曞いおみたしょう。

src/main/java/org/littlewings/infinispan/distexec/protostream/StreamSummaryTask.java

package org.littlewings.infinispan.distexec.protostream;

import java.io.Serializable;
import java.util.stream.Collectors;

import org.infinispan.Cache;
import org.jboss.logging.Logger;
import org.littlewings.infinispan.distexec.protostream.entity.Price;
import org.littlewings.infinispan.distexec.protostream.entity.ProtoBook;
import org.littlewings.infinispan.distexec.protostream.entity.SerializableSummary;

public class StreamSummaryTask implements Serializable {
    private static final long serialVersionUID = 1L;

    Logger logger = Logger.getLogger(StreamSummaryTask.class);

    transient Cache<String, ProtoBook> cache;

    public StreamSummaryTask(Cache<String, ProtoBook> cache) {
        this.cache = cache;
    }

    public SerializableSummary execute() {
        return cache
                .values()
                .stream()
                .map(book -> {
                    logger.infof("[execute] map %s", book.getIsbn());
                    return Price.create(book.getIsbn(), book.getPrice());
                })
                .collect(() ->
                        Collectors.reducing(
                                SerializableSummary.create(0),
                                p -> {
                                    logger.infof("[execute] collect %s", p.getIsbn());
                                    return SerializableSummary.create(p.getValue());
                                },
                                (s1, s2) -> {
                                    logger.infof("[execute] collect %d, %d", s1.getValue(), s2.getValue());
                                    return SerializableSummary.create(s1.getValue() + s2.getValue());
                                })
                );
    }

    public SerializableSummary execute(int greaterThanPrice) {
        return cache
                .values()
                .stream()
                .filter(book -> {
                    logger.infof("[execute, filter price] filter %s", book.getIsbn());
                    return book.getPrice() > greaterThanPrice;
                })
                .map(book -> {
                    logger.infof("[execute, filter price] map %s", book.getIsbn());
                    return Price.create(book.getIsbn(), book.getPrice());
                })
                .collect(() ->
                        Collectors.reducing(
                                SerializableSummary.create(0),
                                p -> {
                                    logger.infof("[execute, filter price] collect %s", p.getIsbn());
                                    return SerializableSummary.create(p.getValue());
                                },
                                (s1, s2) -> {
                                    logger.infof("[execute, filter price] collect %d, %d", s1.getValue(), s2.getValue());
                                    return SerializableSummary.create(s1.getValue() + s2.getValue());
                                })
                );
    }
}

いきなりでなんですが、このクラスがすでにSerializableです。

public class StreamSummaryTask implements Serializable {

凊理の内容自䜓は、Cacheに栌玍されおいるProtoBookクラスのpriceを合算しおいるだけです。

    public SerializableSummary execute() {
        return cache
                .values()
                .stream()
                .map(book -> {
                    logger.infof("[execute] map %s", book.getIsbn());
                    return Price.create(book.getIsbn(), book.getPrice());
                })
                .collect(() ->
                        Collectors.reducing(
                                SerializableSummary.create(0),
                                p -> {
                                    logger.infof("[execute] collect %s", p.getIsbn());
                                    return SerializableSummary.create(p.getValue());
                                },
                                (s1, s2) -> {
                                    logger.infof("[execute] collect %d, %d", s1.getValue(), s2.getValue());
                                    return SerializableSummary.create(s1.getValue() + s2.getValue());
                                })
                );
    }

CacheStream#filterを䜿っおいるバヌゞョンもありたすが。

凊理の内容は単玔ですが、どこでSerializableが求められるのかをわかるようにするために、各皮䞭間・終端操䜜の
戻り倀を自分で甚意したクラスにしおおきたした。

Priceクラスは、こんな定矩です。こちらはSerializableではありたせん。

src/main/java/org/littlewings/infinispan/distexec/protostream/entity/Price.java

package org.littlewings.infinispan.distexec.protostream.entity;

public class Price {
    String isbn;
    int value;

    public static Price create(String isbn, int value) {
        Price price = new Price();

        price.setIsbn(isbn);
        price.setValue(value);

        return price;
    }

    // gettersetterは省略
}

SerializableSummaryクラスは、Serializableを実装したす。

src/main/java/org/littlewings/infinispan/distexec/protostream/entity/SerializableSummary.java

package org.littlewings.infinispan.distexec.protostream.entity;

import java.io.Serializable;

public class SerializableSummary implements Serializable {
    private static final long serialVersionUID = 1L;

    int value;

    public static SerializableSummary create(int value) {
        SerializableSummary summary = new SerializableSummary();

        summary.setValue(value);

        return summary;
    }

    // gettersetter
}

぀たり、CacheStream#collectではSerializableが必芁だ、ずいうこずになりたす。
※ずはいえ、今回の゜ヌスコヌドでシリアラむズが必芁になるのはLoggerをフィヌルドロヌカル倉数でも同じに持ち、それを分散凊理内で参照しおいるからであり、このためにStreamSummaryTaskはSerializableを実装しおいたす

埌にも曞きたすが、CacheStream#mapに぀いおは今回はSerializableは実は求められおいたせん。

テストコヌドは、こちら。

    @Test
    public void distributedStream() {
        this.<String, ProtoBook>withCache("bookCache", 3, cache -> {
            books.forEach(book -> cache.put(book.getIsbn(), book));

            StreamSummaryTask summaryTask = new StreamSummaryTask(cache);

            SerializableSummary totalPriceSummary = summaryTask.execute();
            assertThat(totalPriceSummary.getValue()).isEqualTo(50446);

            SerializableSummary filteredPriceSummary = summaryTask.execute(5000);
            assertThat(filteredPriceSummary.getValue()).isEqualTo(25669);
        });
    }

このテストコヌドを動䜜させるための、Infinispanの蚭定ファむルはこちらです。

src/test/resources/infinispan.xml

<?xml version="1.0" encoding="UTF-8"?>
<infinispan
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="urn:infinispan:config:12.1 https://infinispan.org/schemas/infinispan-config-12.1.xsd"
        xmlns="urn:infinispan:config:12.1">
    <cache-container shutdown-hook="REGISTER">
        <transport cluster="ispn-cluster" stack="udp"/>

        <distributed-cache name="bookCache" owners="1">
            <encoding>
                <!-- <serialization marshaller="org.infinispan.commons.marshall.JavaSerializationMarshaller"> を入れたら明瀺が必芁 -->
                <key media-type="application/x-protostream"/>
                <value media-type="application/x-protostream"/>
            </encoding>
        </distributed-cache>

        <serialization marshaller="org.infinispan.commons.marshall.JavaSerializationMarshaller">
            <!-- <context-initializer class="org.littlewings.infinispan.distexec.protostream.entity.EntityInitializerImpl"/>  自動生成されるので䞍芁 -->
            <allow-list>
                <class>org.littlewings.infinispan.distexec.protostream.StreamSummaryTask</class>
                <class>org.littlewings.infinispan.distexec.protostream.FunctionalMapSummaryTask</class>
                <regex>org\.littlewings\.infinispan\.distexec\.protostream\.entity\.Serializable.+</regex>
                <regex>org\.jboss\.logging\..+</regex>
            </allow-list>
        </serialization>
    </cache-container>
</infinispan>

今回の゜ヌスコヌドを、<serialization>を蚭定しないたた動䜜させようずするず、Distributed Streamを䜿っおいるクラスを
Marshallingしようずしお倱敗したす。

22:40:47.727 [main] WARN  org.infinispan.PERSISTENCE - ISPN000559: Cannot marshall 'class org.infinispan.marshall.protostream.impl.MarshallableUserObject'
java.lang.IllegalArgumentException: No marshaller registered for object of Java type org.littlewings.infinispan.distexec.protostream.StreamSummaryTask : org.littlewings.infinispan.distexec.protostream.StreamSummaryTask@161dd92a

このクラスを、ProtoStreamでシリアラむズするのはさすがにできたせんね 。

このため、MarshallerずしおJavaSerializationMarshallerを指定し、Java暙準のシリアラむズの仕組みも䜿えるように
したす。

        <serialization marshaller="org.infinispan.commons.marshall.JavaSerializationMarshaller">
            <allow-list>
                <class>org.littlewings.infinispan.distexec.protostream.StreamSummaryTask</class>
                <class>org.littlewings.infinispan.distexec.protostream.FunctionalMapSummaryTask</class>
                <regex>org\.littlewings\.infinispan\.distexec\.protostream\.entity\.Serializable.+</regex>
                <regex>org\.jboss\.logging\..+</regex>
            </allow-list>
        </serialization>

この時、シリアラむズできるクラスは明瀺的に指定する必芁があり、regex正芏衚珟たたはclassクラス名で
指定したす。
※次に䜿うクラスの名前FunctionalMapSummaryTaskも含たれおいたすが

Using Java serialization

ここに登録しおいないクラスは、Java暙準のシリアラむズを行う際にInfinispanから拒吊されたす。

ちなみに、SerializationContextInitializerを自分で䜜成しおいたすが、これはServiceLoaderの仕組みで自動登録されるため
今回は指定䞍芁です。

Registering serialization context initializers

        <serialization marshaller="org.infinispan.commons.marshall.JavaSerializationMarshaller">
            <!-- <context-initializer class="org.littlewings.infinispan.distexec.protostream.entity.EntityInitializerImpl"/>  自動生成されるので䞍芁 -->

耇数のSerializationContextInitializerを䜿う堎合、ひず぀でも明瀺的に登録した堎合は自動登録が無効になるため、
すべお明瀺的に指定する必芁があるようです。

そしお、JavaSerializationMarshallerをMarshallerを指定した段階で、特になにもしないずCacheにもJava暙準の
シリアラむズの仕組みで゚ントリを登録しようずしたす。

        <serialization marshaller="org.infinispan.commons.marshall.JavaSerializationMarshaller">

぀たり、ProtoBookがSerializableであるこずを求めおきたす。

そうではなく、ProtoBook、぀たりCacheに栌玍する゚ントリはProtoStreamでMarshallingを行う堎合は、゚ンコヌディングを
明瀺したす。

Encoding caches as ProtoStream

        <distributed-cache name="bookCache" owners="1">
            <encoding>
                <!-- <serialization marshaller="org.infinispan.commons.marshall.JavaSerializationMarshaller"> を入れたら明瀺が必芁 -->
                <key media-type="application/x-protostream"/>
                <value media-type="application/x-protostream"/>
            </encoding>
        </distributed-cache>

これで、CacheにはProtoStreamでMarshallingし぀぀、分散凊理はJava暙準のシリアラむズの仕組みで動䜜するように
蚭定できたした。

先ほどのテストもパスしたす。

Functional Map

次は、Functional Map。今回はReadOnlyMapを䜿うこずにしたした。

Read-Only Map API

src/main/java/org/littlewings/infinispan/distexec/protostream/FunctionalMapSummaryTask.java

package org.littlewings.infinispan.distexec.protostream;

import java.io.Serializable;
import java.util.Set;
import java.util.stream.Collectors;

import org.infinispan.Cache;
import org.infinispan.functional.FunctionalMap;
import org.infinispan.functional.Traversable;
import org.infinispan.functional.impl.FunctionalMapImpl;
import org.infinispan.functional.impl.ReadOnlyMapImpl;
import org.jboss.logging.Logger;
import org.littlewings.infinispan.distexec.protostream.entity.ProtoBook;
import org.littlewings.infinispan.distexec.protostream.entity.SerializablePrice;
import org.littlewings.infinispan.distexec.protostream.entity.Summary;

public class FunctionalMapSummaryTask implements Serializable {
    private static final long serialVersionUID = 1L;

    Logger logger = Logger.getLogger(FunctionalMapSummaryTask.class);

    transient Cache<String, ProtoBook> cache;

    public FunctionalMapSummaryTask(Cache<String, ProtoBook> cache) {
        this.cache = cache;
    }

    public Summary execute(Set<String> isbns) {
        FunctionalMapImpl<String, ProtoBook> functionalMap = FunctionalMapImpl.create(cache.getAdvancedCache());
        FunctionalMap.ReadOnlyMap<String, ProtoBook> readOnlyMap = ReadOnlyMapImpl.create(functionalMap);

        Traversable<SerializablePrice> prices = readOnlyMap.evalMany(isbns, view -> {
            String isbn = view.key();
            ProtoBook book = view.get();

            logger.infof("[execute] get %s", isbn);
            return SerializablePrice.create(view.key(), book.getPrice());
        });

        return prices
                .collect(
                        Collectors.reducing(
                                Summary.create(0),
                                p -> {
                                    logger.infof("[execute] collect %s", p.getIsbn());
                                    return Summary.create(p.getValue());
                                },
                                (s1, s2) -> {
                                    logger.infof("[execute] collect %d, %d", s1.getValue(), s2.getValue());
                                    return Summary.create(s1.getValue() + s2.getValue());
                                })
                );
    }
}

Functional Mapの堎合、eval〜メ゜ッドで扱う凊理がSerializableである必芁があるようです。なので、メ゜ッドの戻り倀に
䜿う倀は今回はSerializableになりたした。

src/main/java/org/littlewings/infinispan/distexec/protostream/entity/SerializablePrice.java

package org.littlewings.infinispan.distexec.protostream.entity;

import java.io.Serializable;

public class SerializablePrice implements Serializable {
    private static final long serialVersionUID = 1L;

    String isbn;
    int value;

    public static SerializablePrice create(String isbn, int value) {
        SerializablePrice price = new SerializablePrice();

        price.setIsbn(isbn);
        price.setValue(value);

        return price;
    }

    // gettersetterは省略
}

Traversable#collectを䜿っおも、こちらはロヌカルで動䜜するようで、Serializableは求められたせん。

src/main/java/org/littlewings/infinispan/distexec/protostream/entity/Summary.java

package org.littlewings.infinispan.distexec.protostream.entity;

public class Summary {
    int value;

    public static Summary create(int value) {
        Summary summary = new Summary();

        summary.setValue(value);

        return summary;
    }

    // gettersetterは省略
}

テストコヌドは、こちら。

    @Test
    public void functionalMap() {
        this.<String, ProtoBook>withCache("bookCache", 3, cache -> {
            books.forEach(book -> cache.put(book.getIsbn(), book));

            FunctionalMapSummaryTask summaryTask = new FunctionalMapSummaryTask(cache);

            Set<String> isbns = books.stream().map(ProtoBook::getIsbn).collect(Collectors.toSet());

            Summary totalPriceSummary = summaryTask.execute(isbns);
            assertThat(totalPriceSummary.getValue()).isEqualTo(50446);
        });
    }

Infinispanの蚭定は、Distributed Streamの時ず同じです含めおいたす。

もう少し

ずころで、ここたでSerializableなものがたくさん出おきたした。

実は、もうちょっず気を぀けるず同じ凊理でSerializableであるものを枛らすこずができたす。

Serializable

Distributed Streamを扱ったクラスを、もうちょっず倉えおみたしょう。

src/main/java/org/littlewings/infinispan/distexec/protostream/StreamSummaryReturnOnlySerializableTask.java

package org.littlewings.infinispan.distexec.protostream;

import java.util.stream.Collectors;

import org.infinispan.Cache;
import org.jboss.logging.Logger;
import org.littlewings.infinispan.distexec.protostream.entity.Price;
import org.littlewings.infinispan.distexec.protostream.entity.ProtoBook;
import org.littlewings.infinispan.distexec.protostream.entity.SerializableSummary;

public class StreamSummaryReturnOnlySerializableTask {
    Cache<String, ProtoBook> cache;

    public StreamSummaryReturnOnlySerializableTask(Cache<String, ProtoBook> cache) {
        this.cache = cache;
    }

    public SerializableSummary execute() {
        return cache
                .values()
                .stream()
                .map(book -> {
                    Logger.getLogger(StreamSummaryReturnOnlySerializableTask.class).infof("[execute, filter price] map %s", book.getIsbn());
                    return Price.create(book.getIsbn(), book.getPrice());
                })
                .collect(() ->
                        Collectors.reducing(
                                SerializableSummary.create(0),
                                p -> {
                                    Logger.getLogger(StreamSummaryReturnOnlySerializableTask.class).infof("[execute] collect %s", p.getIsbn());
                                    return SerializableSummary.create(p.getValue());
                                },
                                (s1, s2) -> {
                                    Logger.getLogger(StreamSummaryReturnOnlySerializableTask.class).infof("[execute] collect %d, %d", s1.getValue(), s2.getValue());
                                    return SerializableSummary.create(s1.getValue() + s2.getValue());
                                })
                );
    }

    public SerializableSummary execute(int greaterThanPrice) {
        return cache
                .values()
                .stream()
                .filter(book -> {
                    Logger.getLogger(StreamSummaryReturnOnlySerializableTask.class).infof("[execute, filter price] filter %s", book.getIsbn());
                    return book.getPrice() > greaterThanPrice;
                })
                .map(book -> {
                    Logger.getLogger(StreamSummaryReturnOnlySerializableTask.class).infof("[execute, filter price] map %s", book.getIsbn());
                    return Price.create(book.getIsbn(), book.getPrice());
                })
                .collect(() ->
                        Collectors.reducing(
                                SerializableSummary.create(0),
                                p -> {
                                    Logger.getLogger(StreamSummaryReturnOnlySerializableTask.class).infof("[execute, filter price] collect %s", p.getIsbn());
                                    return SerializableSummary.create(p.getValue());
                                },
                                (s1, s2) -> {
                                    Logger.getLogger(StreamSummaryReturnOnlySerializableTask.class).infof("[execute, filter price] collect %d, %d", s1.getValue(), s2.getValue());
                                    return SerializableSummary.create(s1.getValue() + s2.getValue());
                                })
                );
    }
}

クラスの定矩から、Serializableを倖したした。フィヌルドに定矩しおいるCacheも、transientではなくなっおいたす。

public class StreamSummaryReturnOnlySerializableTask {
    Cache<String, ProtoBook> cache;

たた、ログ出力はCacheStreamのメ゜ッド内でLoggerを取埗しお行うようにしおいたす。

    public SerializableSummary execute() {
        return cache
                .values()
                .stream()
                .map(book -> {
                    Logger.getLogger(StreamSummaryReturnOnlySerializableTask.class).infof("[execute, filter price] map %s", book.getIsbn());
                    return Price.create(book.getIsbn(), book.getPrice());
                })
                .collect(() ->
                        Collectors.reducing(
                                SerializableSummary.create(0),
                                p -> {
                                    Logger.getLogger(StreamSummaryReturnOnlySerializableTask.class).infof("[execute] collect %s", p.getIsbn());
                                    return SerializableSummary.create(p.getValue());
                                },
                                (s1, s2) -> {
                                    Logger.getLogger(StreamSummaryReturnOnlySerializableTask.class).infof("[execute] collect %d, %d", s1.getValue(), s2.getValue());
                                    return SerializableSummary.create(s1.getValue() + s2.getValue());
                                })
                );
    }

こうするず、CacheStream#collectメ゜ッドの戻り倀だけがSerializableであれば良くなりたす。

䞊蚘クラスを動かすためのInfinispanの蚭定は、こちら。

src/test/resources/infinispan-serializable.xml

<?xml version="1.0" encoding="UTF-8"?>
<infinispan
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="urn:infinispan:config:12.1 https://infinispan.org/schemas/infinispan-config-12.1.xsd"
        xmlns="urn:infinispan:config:12.1">
    <cache-container shutdown-hook="REGISTER">
        <transport cluster="ispn-cluster" stack="udp"/>

        <distributed-cache name="bookCache" owners="1">
            <encoding>
                <key media-type="application/x-protostream"/>
                <value media-type="application/x-protostream"/>
            </encoding>
        </distributed-cache>

        <serialization marshaller="org.infinispan.commons.marshall.JavaSerializationMarshaller">
            <allow-list>
                <class>org.littlewings.infinispan.distexec.protostream.entity.SerializableSummary</class>
            </allow-list>
        </serialization>
    </cache-container>
</infinispan>

テストコヌドは、最埌にたずめお茉せたす。

ProtoStreamにする

ここたでくるず、CacheStream#collectの戻り倀をProtoStreamにするこずもできるのではずいう気もしたす。

できたす。

src/main/java/org/littlewings/infinispan/distexec/protostream/StreamSummaryReturnOnlyProtoTask.java

package org.littlewings.infinispan.distexec.protostream;

import java.util.stream.Collectors;

import org.infinispan.Cache;
import org.jboss.logging.Logger;
import org.littlewings.infinispan.distexec.protostream.entity.Price;
import org.littlewings.infinispan.distexec.protostream.entity.ProtoBook;
import org.littlewings.infinispan.distexec.protostream.entity.ProtoSummary;

public class StreamSummaryReturnOnlyProtoTask {
    Cache<String, ProtoBook> cache;

    public StreamSummaryReturnOnlyProtoTask(Cache<String, ProtoBook> cache) {
        this.cache = cache;
    }

    public ProtoSummary execute() {
        return cache
                .values()
                .stream()
                .map(book -> {
                    Logger.getLogger(StreamSummaryReturnOnlyProtoTask.class).infof("[execute, filter price] map %s", book.getIsbn());
                    return Price.create(book.getIsbn(), book.getPrice());
                })
                .collect(() ->
                        Collectors.reducing(
                                ProtoSummary.create(0),
                                p -> {
                                    Logger.getLogger(StreamSummaryReturnOnlyProtoTask.class).infof("[execute] collect %s", p.getIsbn());
                                    return ProtoSummary.create(p.getValue());
                                },
                                (s1, s2) -> {
                                    Logger.getLogger(StreamSummaryReturnOnlyProtoTask.class).infof("[execute] collect %d, %d", s1.getValue(), s2.getValue());
                                    return ProtoSummary.create(s1.getValue() + s2.getValue());
                                })
                );
    }

    public ProtoSummary execute(int greaterThanPrice) {
        return cache
                .values()
                .stream()
                .filter(book -> {
                    Logger.getLogger(StreamSummaryReturnOnlyProtoTask.class).infof("[execute, filter price] filter %s", book.getIsbn());
                    return book.getPrice() > greaterThanPrice;
                })
                .map(book -> {
                    Logger.getLogger(StreamSummaryReturnOnlyProtoTask.class).infof("[execute, filter price] map %s", book.getIsbn());
                    return Price.create(book.getIsbn(), book.getPrice());
                })
                .collect(() ->
                        Collectors.reducing(
                                ProtoSummary.create(0),
                                p -> {
                                    Logger.getLogger(StreamSummaryReturnOnlyProtoTask.class).infof("[execute, filter price] collect %s", p.getIsbn());
                                    return ProtoSummary.create(p.getValue());
                                },
                                (s1, s2) -> {
                                    Logger.getLogger(StreamSummaryReturnOnlyProtoTask.class).infof("[execute, filter price] collect %d, %d", s1.getValue(), s2.getValue());
                                    return ProtoSummary.create(s1.getValue() + s2.getValue());
                                })
                );
    }
}

CacheStream#collectで䜿うクラスを、ProtoSummaryずいうクラスにしたした。ProtoStreamでMarshallingするこずを
想定したクラスです。

src/main/java/org/littlewings/infinispan/distexec/protostream/entity/ProtoSummary.java

package org.littlewings.infinispan.distexec.protostream.entity;

import org.infinispan.protostream.annotations.ProtoFactory;
import org.infinispan.protostream.annotations.ProtoField;

public class ProtoSummary {
    @ProtoField(number = 1, defaultValue = "0")
    int value;

    @ProtoFactory
    public static ProtoSummary create(int value) {
        ProtoSummary summary = new ProtoSummary();

        summary.setValue(value);

        return summary;
    }

    // gettersetterは省略
}

SerializationContextInitializerで、このクラスも察象に远加。

src/main/java/org/littlewings/infinispan/distexec/protostream/entity/EntityInitializer.java

package org.littlewings.infinispan.distexec.protostream.entity;

import org.infinispan.protostream.SerializationContextInitializer;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;

@AutoProtoSchemaBuilder(
        includeClasses = {ProtoBook.class, ProtoSummary.class},
        schemaFileName = "entities.proto",
        schemaFilePath = "proto",
        schemaPackageName = "entity"
)
public interface EntityInitializer extends SerializationContextInitializer {
}

こうするず、Infinispanの蚭定からserializationを削陀するこずができたす。encodingも倖せたす。

src/test/resources/infinispan-proto.xml

<?xml version="1.0" encoding="UTF-8"?>
<infinispan
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="urn:infinispan:config:12.1 https://infinispan.org/schemas/infinispan-config-12.1.xsd"
        xmlns="urn:infinispan:config:12.1">
    <cache-container shutdown-hook="REGISTER">
        <transport cluster="ispn-cluster" stack="udp"/>

        <distributed-cache name="bookCache" owners="1"/>
    </cache-container>
</infinispan>
テストコヌド

ここたでの2皮類のDistributed Streamsを扱うテストコヌドは、こちら。

src/test/java/org/littlewings/infinispan/distexec/protostream/ProtoStreamDistExecSimplyTest.java

package org.littlewings.infinispan.distexec.protostream;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.infinispan.Cache;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.junit.jupiter.api.Test;
import org.littlewings.infinispan.distexec.protostream.entity.ProtoBook;
import org.littlewings.infinispan.distexec.protostream.entity.ProtoSummary;
import org.littlewings.infinispan.distexec.protostream.entity.SerializableSummary;

import static org.assertj.core.api.Assertions.assertThat;

public class ProtoStreamDistExecSimplyTest {
    List<ProtoBook> books =
            List.of(
                    ProtoBook.create("978-1782169970", "Infinispan Data Grid Platform Definitive Guide", 5344),
                    ProtoBook.create("978-1849518222", "Infinispan Data Grid Platform", 3608),
                    ProtoBook.create("978-0359439379", "The Apache Ignite Book", 7686),
                    ProtoBook.create("978-1365732355", "High Performance in-memory computing with Apache Ignite", 6342),
                    ProtoBook.create("978-1789347531", "Apache Ignite Quick Start Guide: Distributed data caching and processing made easy", 3638),
                    ProtoBook.create("978-1785285332", "Getting Started with Hazelcast - Second Edition: Get acquainted with the highly scalable data grid, Hazelcast, and learn how to bring its powerful in-memory features into your application", 4209),
                    ProtoBook.create("978-1617295522", "Spark in Action, Second Edition: Covers Apache Spark 3 with Examples in Java, Python, and Scala", 6297),
                    ProtoBook.create("978-1484257807", "Beginning Apache Spark Using Azure Databricks: Unleashing Large Cluster Analytics in the Cloud", 4817),
                    ProtoBook.create("978-1788997829", "Apache Kafka Quick Start Guide: Leverage Apache Kafka 2.0 to simplify real-time data processing for distributed applications", 3516),
                    ProtoBook.create("978-1491936160", "Kafka: The Definitive Guide: Real-Time Data and Stream Processing at Scale", 4989)
            );

    <K, V> void withCache(String configurationXmlPath, String cacheName, int numInstances, Consumer<Cache<K, V>> func) {
        List<EmbeddedCacheManager> managers =
                IntStream
                        .rangeClosed(1, numInstances)
                        .mapToObj(i -> {
                            try {
                                return new DefaultCacheManager(configurationXmlPath);
                            } catch (IOException e) {
                                throw new UncheckedIOException(e);
                            }
                        })
                        .collect(Collectors.toList());

        managers.forEach(manager -> manager.getCache(cacheName));

        try {
            Cache<K, V> cache = managers.get(0).getCache(cacheName);

            func.accept(cache);
        } finally {
            managers.forEach(manager -> manager.stop());
        }
    }

    @Test
    public void returnOnlySerializable() {
        this.<String, ProtoBook>withCache("infinispan-serializable.xml", "bookCache", 3, cache -> {
            books.forEach(book -> cache.put(book.getIsbn(), book));

            StreamSummaryReturnOnlySerializableTask summaryTask = new StreamSummaryReturnOnlySerializableTask(cache);

            SerializableSummary totalPriceSummary = summaryTask.execute();
            assertThat(totalPriceSummary.getValue()).isEqualTo(50446);

            SerializableSummary filteredPriceSummary = summaryTask.execute(5000);
            assertThat(filteredPriceSummary.getValue()).isEqualTo(25669);
        });
    }

    @Test
    public void returnOnlyProto() {
        this.<String, ProtoBook>withCache("infinispan-proto.xml", "bookCache", 3, cache -> {
            books.forEach(book -> cache.put(book.getIsbn(), book));

            StreamSummaryReturnOnlyProtoTask summaryTask = new StreamSummaryReturnOnlyProtoTask(cache);

            ProtoSummary totalPriceSummary = summaryTask.execute();
            assertThat(totalPriceSummary.getValue()).isEqualTo(50446);

            ProtoSummary filteredPriceSummary = summaryTask.execute(5000);
            assertThat(filteredPriceSummary.getValue()).isEqualTo(25669);
        });
    }
}

今回は、Cacheを䜿う時にInfinispanの蚭定ファむルも指定できるようにしおいたす。

        this.<String, ProtoBook>withCache("infinispan-serializable.xml", "bookCache", 3, cache -> {


        this.<String, ProtoBook>withCache("infinispan-proto.xml", "bookCache", 3, cache -> {

こんなずころでしょうか。

たずめ

CacheにProtoStreamでMarshallingした゚ントリを栌玍し぀぀、分散凊理を䜿う堎合のMarshallingはどうなるのか
ずいうこずをいく぀かバリ゚ヌションを入れお確認しおみたした。

結果を芋れば、分散凊理に枡すLambda匏たたはむンスタンスにMarshallingが必芁になるものを絞り蟌み、
か぀すべおProtoStreamでMarshalling可胜なように䜜成すれば、ProtoStreamでMarshallingの仕組みを統䞀するこずも
できるずいうこずは蚀えそうです。

ただ、ProtoStreamでMarshallingするのが難しそうなものや、サヌドパヌティ補のラむブラリのクラスなどを扱う堎合に
どうしようずいう感じでしょうか。

サヌドパヌティ補のものであっおも、可胜なそうであれば@ProtoAdapterアノテヌションを䜿っおProtoStreamでの
Marshallingはできそうですけどね。

Creating ProtoStream adapter classes

いけるずころたでは、ProtoStreamでMarshallingするように寄せおいった方がいいのでしょうか
このブログでは、いったんその方向で頑匵っおみたすか。

今回䜜成した゜ヌスコヌドは、こちらに眮いおいたす。

https://github.com/kazuhira-r/infinispan-getting-started/tree/master/embedded-distexec-protostream