CLOVER🍀

That was when it all began.

Payara MicroのClustered CDI Event Busが初期化処理が不要になり、loopbackができるようになったという話

以前、Payara MicroのClustered CDI Event Busについて、エントリーを書きました。

Payara MicroのClustered CDI Event Bus(Hazelcast Topic)の仕組み - CLOVER

CDIのEvent APIで、あるPayara Microで発生したイベントを、別のPayara Microに伝播させる機能です。
内部的には、HazelcastのTopicを使って実現しています。

このClustered CDI Event Busですが、最初に出てきた時には

  • 初期化処理(ClusteredCDIEventBus#initializeを呼び出す)を行う必要がある
  • イベントが発生したNodeには、イベントが通知されない

という点がありました。

初期化はまあいいとして、自Nodeで発生したイベントが拾えてもいいんじゃないのかなぁと思っていたのですが、
Payara 171でloopbackができるようになっていたみたいです。

What's new in Payara Micro 171?

また、初期化処理も不要になったようです。

ちょっと試してみましょう。

準備

Mavenの定義は、こんな感じで。WARファイルの名前は、「app」とします。

    <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>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>fish.payara.extras</groupId>
            <artifactId>payara-micro</artifactId>
            <version>4.1.2.172</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>fish.payara.api</groupId>
            <artifactId>payara-api</artifactId>
            <version>4.1.2.172</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>app</finalName>
    </build>

以前は「payara-micro」ひとつあれば良かった気がしますが、「javaee-api」も要るようになったんですねぇ。
Clustered CDI Event Busを使うには、追加で「payara-api」も必要になります。

Public API

サンプルアプリケーションを実装する

では、Clustered CDI Event Busを使ったサンプルアプリケーションを実装してみます。

Clustered CDI Event Busについてのドキュメントは、こちら。

Remote CDI Events in Payara Micro

Firing and Listening for remote CDI Events

サンプルアプリケーションは、前回のエントリーと似たような構成にしましょう。基本的な考え方、構成は変わっていないので、Clustered CDI Event Busに
関するAPIの使い方は、前回のエントリーを参照してください。

JAX-RSの有効化。
src/main/java/org/littlewings/payara/JaxrsActivator.java

package org.littlewings.payara;

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

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

イベントの送受信で使われる、メッセージ用のクラス。
src/main/java/org/littlewings/payara/EventMessage.java

package org.littlewings.payara;

import java.io.Serializable;

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

    private String value;

    private String memberId;

    public EventMessage(String value, String memberId) {
        this.value = value;
        this.memberId = memberId;
    }

    // getter/setterは省略
}

イベントを発火させる側のクラス。
src/main/java/org/littlewings/payara/EventSendResource.java

package org.littlewings.payara;

import javax.enterprise.context.RequestScoped;
import javax.enterprise.event.Event;
import javax.inject.Inject;
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;

import fish.payara.micro.PayaraMicroRuntime;
import fish.payara.micro.cdi.Outbound;

@Path("event")
@RequestScoped
public class EventSendResource {
    @Inject
    @Outbound
    Event<EventMessage> event;

    @Inject
    PayaraMicroRuntime runtime;

    @GET
    @Path("send")
    @Produces(MediaType.TEXT_PLAIN)
    public String send(@QueryParam("message") String message) {
        event.fire(new EventMessage(message, runtime.getLocalDescriptor().getMemberUUID()));

        return "OK!";
    }
}

Clustered CDI Event Busでイベントを送信するには、@Outboundアノテーションを付与します。

    @Inject
    @Outbound
    Event<EventMessage> event;

受信側は、こちら。
src/main/java/org/littlewings/payara/EventMessageReceiver.java

package org.littlewings.payara;

import javax.enterprise.context.Dependent;
import javax.enterprise.event.Observes;

import fish.payara.micro.cdi.Inbound;

@Dependent
public class EventMessageReceiver {
    public void observe(@Observes @Inbound EventMessage event) {
        System.out.printf("received message = %s, member = %s%n", event.getValue(), event.getMemberId());
    }
}

Clustered CDI Event Busでのイベント受信時には、@Observersアノテーションとともに、@Inboundアノテーションが付与されていればOKです。

    public void observe(@Observes @Inbound EventMessage event) {

とりあえず、ここまでで最低限Clustered CDI Event Busを使うための準備ができました。

動かしてみる

では、パッケージングして起動してみます。Node数は、2つとしましょう。

$ mvn package

## Node 1
$ java -jar payara-micro-4.1.2.172.jar --deploy target/app.war

## Node 2
$ java -jar payara-micro-4.1.2.172.jar --deploy target/app.war --port 9080

curlでアクセスしてみます。リクエストは、最初のNodeに送ります。

$ curl http://localhost:8080/app/event/send?message=Hello
OK!

2つのNodeでどうなっているかというと

## Node 1

## Node 2
received message = Hello, member = 55e0a12e-655f-4dc4-9d18-57943137ff86

となり、2つ目のNodeでしかイベントを受け取っていないことになります。これは、クラスタを構成せず単一のNodeで実行しても同じで、
イベントを発火させたNode自身はイベントを受け取れません。

これが、171以前の挙動だったのですが、@Outboundアノテーションにloopbackパラメーターが追加され、これをtrueにすることで自Nodeも
イベントを受け取ることができるようになりました。

こんな感じです。

    @Inject
    @Outbound(loopBack = true)
    Event<EventMessage> event;

再度パッケージングして起動、リクエストを送ると、今度は2 Nodeともイベントを受け取れていることが確認できます。

## Node 1
received message = Hello, member = c8411200-d660-47e2-8b44-57a4f2a5fe3b

## Node 2
received message = Hello, member = c8411200-d660-47e2-8b44-57a4f2a5fe3b

loopbackが機能していることが確認できました。

なにが変わったんでしょうか?

とりあえず、@Outboundアノテーションにloopbackが追加されたことにより、これをチェックしてloopbackがtrueであれば自Nodeにもイベントを
発火させるように実装が変わりました。
https://github.com/payara/Payara/blob/payara-server-4.1.2.172/appserver/payara-appserver-modules/payara-micro-service/src/main/java/fish/payara/appserver/micro/services/PayaraInstanceImpl.java#L178

まあ、そりゃそうですよね…。

Topicに対してのListenerは、そんなに変わっていません。
https://github.com/payara/Payara/blob/payara-server-4.1.2.172/nucleus/payara-modules/hazelcast-bootstrap/src/main/java/fish/payara/nucleus/eventbus/TopicListener.java

ところで、このエントリーの最初でも述べましたが、Clustered CDI Event Busを使うにあたっての初期化処理(ClusteredCDIEventBus#initializeの呼び出し)が
なくなりました。前回は初期化用のServletContextListenerを用意していましたが、今回は不要になりました。

確かに、ドキュメントにも要らなくなったと書かれています。

Manual initialization of the event bus

Before version of 4.1.1.171, it was necessary to initialize the event bus using ClusteredCDIEventBus.initialize() manually upon startup. Although this method is still available in the API, it is not necessary anymore, since the event bus is automatically initialized after an application is started.

https://payara.gitbooks.io/payara-server/content/documentation/payara-micro/cdi-events.html

Payara Microのサンプルからも、ClusteredCDIEventBus#initializeの呼び出しは削除されました。
https://github.com/payara/Payara-Examples/blob/master/Payara-Micro/cdi-clustered-events/event-sender/src/main/java/fish/payara/examples/payaramicro/event/sender/SendEventServlet.java#L119-L122

Payara側でどういう実装になったかというと、ServletContextの初期化に合わせて自動的にinitializeを呼び出すように変更されたみたいです。
https://github.com/payara/Payara/blob/payara-server-4.1.2.172/appserver/payara-appserver-modules/payara-micro-cdi/src/main/java/fish/payara/micro/cdi/extension/ClusteredCDIEventBusImpl.java#L117-L122

    public void onStart(@Observes @Initialized(ApplicationScoped.class) ServletContext init) {
       initialize();
       if (runtime.isClustered()) {
            Logger.getLogger(ClusteredCDIEventBusImpl.class.getName()).log(Level.INFO, "Clustered CDI Event bus initialized for " + init.getContextPath());
        }
    }

ところで、以前はどうしてClusteredCDIEventBus#initializeの呼び出しが必要だったかというと、この呼び出し時にClassLoaderを
キャプチャしてTopicのイベント受信時にデシリアライズで問題ならないようにしていたからです。

で、これが現在のPayaraでどうなったかというと、前にキャプチャしたClassLoaderを使っていた箇所はManagedExecutorServiceを使った処理に
ごっそり置き換えられました。
https://github.com/payara/Payara/blob/payara-server-4.1.2.172/appserver/payara-appserver-modules/payara-micro-cdi/src/main/java/fish/payara/micro/cdi/extension/ClusteredCDIEventBusImpl.java#L164-L209

この時、JavaEEContextUtilという内部のインターフェースの実装を使っているのですが、この中で前と似たようなことをやっています。
https://github.com/payara/Payara/blob/payara-server-4.1.2.172/appserver/payara-appserver-modules/payara-micro-service/src/main/java/fish/payara/appserver/context/JavaEEContextUtilImpl.java

こういうカラクリで、初期化処理を不要にしたみたいですね。

まとめ

初期化処理が不要になり、loopbackもできるようになったPayara MicroのClustered CDI Event Busを試してみました。

この機能そのものよりも、Payara Microの構成が変わっていたりしたことの方がてこずったりしたのですが、そこはクリアできたので
まあ良しと…。

あと、Payara側の初期化処理の実装を見て、ServletContextとCDIのEventを組み合わせて起動時の処理をするって発想を知らなかったので
勉強になりましたね。