Sending a Hello from Mutiny in Wildfly
MicroProfile Reactive Messaging and SmallRye Mutiny
Recently I was learning about Mutiny to show how to use it together with MicroProfile Reactive Messaging (a.k.a. MP Messaging) in a presentation at work (Worldline Switzerland), and many Mutiny examples are using Quarkus. But we’re using not only Quarkus, there are still many of our projects relying on Wildfly, so I tried to get the examples running with Wildfly.
There is a Wildfly Blog post by Kabir Khan about using
MicroProfile Reactive Messaging with Kafka
but it is using the @Channel
annotation together an Emitter
(and not Mutiny):
@Channel("to-kafka")
private Emitter<String> emitter;
My idea was to use the @Outgoing
annotation together with a Multi
as shown in this example from the
SmallRye Reactive Messaging Documentation
@Outgoing("prices-out")
public Multi<Double> generate() {
return Multi.createFrom().ticks().every(Duration.ofSeconds(1))
.map(x -> random.nextDouble());
}
Mutiny
Mutiny 2 is a library for reactive programming, based on the Java 9 Flow API.
Basically the library contains just two
reactive types
Multi
and Uni
(and the name Mutiny is a contraction of multi and uni).
The Multi
type is what the example above uses to generate a stream with many items.
Using Wildfly
I wanted to keep this example application very minimal, so there is no REST endpoint to observe it, and the solution needs 5 files
- two Java classes (an “outgoing” one and an “incoming” one)
- the MicroProfile configuration file
- the Wildfly configuration file
- the Maven POM to build and deploy the application
You can find the example application on GitHub.
Ticker.java – Populating a Channel
After a 5 second startup delay, the Ticker
class sends out every second an incrementing number
(the “message”) to a channel (named to-kafka
):
@Outgoing("to-kafka")
public Multi<String> generate() {
return Multi
.createFrom().ticks()
.startingAfter(Duration.ofSeconds(5L))
.every(Duration.ofSeconds(1L))
.onItem().transform(i -> Long.toString(i));
}
Once again the Mutiny type Multi
is used to generate the stream
of incrementing numbers.
Note that methods with an @Outgoing
or @Incoming
annotation
are automatically called by the Reactive Messaging framework,
you should not call them from your own code.
Listener.java – Consume a Channel
Of course there should be also a place where the numbers that are sent out
in the Ticker
, are received and consumed, using an @Incoming
channel.
This happens in the Listener
class (nothing fancy, just writing out the
received number):
@Incoming("from-kafka")
public void receive(String feed) {
System.out.printf("Hello Wildfly from Mutiny Ticker %s%n", feed);
}
Connecting the Channels to a Kafka Topic
Until now we have two channels (they are separate, as they have different names), but they are kind of hanging in the air (not connected to anything)
To connect the channels to a Kafka topic, MP Messaging uses
MP Config, usually
by means of a microprofile-config.properties
file:
|
|
Line 4 tells that the channel to_kafka
writes to the Kafka topic hello-wildfly
and line 8 that the channel from_kafka
consumes from the same topic:
Configuring Wildfly
The configuration is based on the standalone-microprofile.xml
that comes with Wildfly;
but for Reactive Messaging two subsystems must explicitly be enabled:
- MicroProfile Reactive Messaging subsystem
- MicroProfile Reactive Streams Operators subsystem
I copied standalone-microprofile.xml
into the project and then added some lines
to enable the additional subsystems (Kabir Khan shows in his blog
the Wildfly CLI commands that have the same result).
In the <extensions>
part of standalone.xml
add two lines for the two MP Reactive extensions
reactive-messaging-smallrye
and reactive-streams-operators-smallrye
:
35 <extension module="org.wildfly.extension.microprofile.jwt-smallrye"/>
36 <extension module="org.wildfly.extension.microprofile.openapi-smallrye"/>
37 <extension module="org.wildfly.extension.microprofile.reactive-messaging-smallrye"/>
38 <extension module="org.wildfly.extension.microprofile.reactive-streams-operators-smallrye"/>
39 <extension module="org.wildfly.extension.microprofile.telemetry"/>
and then further down add two lines in the <profiles>
section for the subsystems
microprofile-reactive-messaging-smallrye
and microprofile-reactive-streams-operators-smallrye
:
340 <subsystem xmlns="urn:wildfly:microprofile-jwt-smallrye:1.0"/>
341 <subsystem xmlns="urn:wildfly:microprofile-openapi-smallrye:1.0"/>
342 <subsystem xmlns="urn:wildfly:microprofile-reactive-messaging-smallrye:1.0"/>
343 <subsystem xmlns="urn:wildfly:microprofile-reactive-streams-operators-smallrye:1.0"/>
344 <subsystem xmlns="urn:wildfly:microprofile-telemetry:1.0"/>
When you use a standalone.xml
file in your project (as opposed to in the
standalone/configuration
directory of the Wildfly installation), you’ll have to start Wildfly
slightly differently to tell it where to find to configuration file, see below.
Maven
You need three dependencies in the pom.xml
file
<dependencies>
<!-- actually needed EE and MP dependencies, from the Wildfly MP BOM -->
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.microprofile.reactive.messaging</groupId>
<artifactId>microprofile-reactive-messaging-api</artifactId>
<scope>provided</scope>
</dependency>
<!-- dependencies needed in the WAR -->
<dependency>
<!-- The version is managed by Wildfly's BOM -->
<groupId>io.smallrye.reactive</groupId>
<artifactId>mutiny</artifactId>
</dependency>
</dependencies>
Note that jakarta.enterprise.cdi-api
and microprofile-reactive-messaging-api
are part of Wildfly “public modules” and managed by the
Wildfly MicroProfile BOM, so you should use provided
scope and not specify a version.
But the io.smallrye.reactive:mutiny
dependency is, although shipped with Wildfly,
only used internally and not visible for applications, so you must not use scope provided
.
It’s version is still managed by the Wildfly BOM though, no need to specify the version yourself.
There is another part of the pom.xml
that I want to mention:
<plugin>
<groupId>org.wildfly.plugins</groupId>
<artifactId>wildfly-maven-plugin</artifactId>
<version>${wildfly.plugin.version}</version>
</plugin>
The Wildfly Maven Plugin is used in the instructions below to deploy the application into a running Wildfly server, but that is only scratching on the surface of its possibilities.
Run the Example
Prerequisites: Java 17, Maven (3.8.x works fine) and either Kafka or Docker (to run Kafka in a container).
I’m using two command line sessions. One to host the Wildfly server and the other to compile and control the application.
First session
-
Download the latest Wildfly (Wildfly 28 as of May 2023) and unpack it into a directory of your choice.
-
Clone the repository with the source code
Cloning the repo in session 1 - bash
- cmd
1
2
3git clone https://github.com/pe-st/wildfly-hello-mutiny.git cd wildfly-hello-mutiny
-
Start a local Kafka if you don’t have one running anyway. I’m using docker for that but that’s up to you. You can also use a remote Kafka if you adapt the project’s
microprofile-config.properties
accordingly.The project provides a
docker-compose.yml
file which makes starting Kafka using docker easy:Session 1, still in directory wildfly-hello-mutiny - all
1
2
3# the '--ansi never' is optional, for better contrast (YMMV) docker compose --ansi never up -d
-
Start Wildfly
Session 1, still in directory wildfly-hello-mutiny - bash
- cmd
1
2
3
4# use the directory where you unpacked Wildfly export WILDFLY_HOME=~/Documents/wildfly-28.0.0.Final $WILDFLY_HOME/bin/standalone.sh --read-only-server-config=src/main/wildfly/standalone.xml
Note the additional parameter to specify the
standalone.xml
from the project and not the Wildfly standard one.
Second session
Open a second command line session (in the first one wildfly is running in the foreground)
-
compile the application:
cd wildfly-hello-mutiny mvn package
-
deploy the application to Wildfly (using the Wildfly Maven Plugin)
mvn wildfly:deploy # after a couple of seconds you should see this in the output from Maven [INFO] --- wildfly-maven-plugin:4.1.0.Final:deploy (default-cli) @ wildfly-hello-mutiny --- [INFO] JBoss Threads version 2.4.0.Final [INFO] JBoss Remoting version 5.0.27.Final [INFO] XNIO version 3.8.9.Final [INFO] XNIO NIO Implementation Version 3.8.9.Final [INFO] ELY00001: WildFly Elytron version 2.1.0.Final [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------
-
Now switch to the first session and observe the output. After some seconds you’ll see this output:
(Kafka initialization messages) 17:27:49,907 INFO [stdout] (vert.x-eventloop-thread-0) Hello Wildfly from Mutiny Ticker 0 17:27:49,911 INFO [stdout] (vert.x-eventloop-thread-0) Hello Wildfly from Mutiny Ticker 1 17:27:50,893 INFO [stdout] (vert.x-eventloop-thread-0) Hello Wildfly from Mutiny Ticker 2 17:27:51,892 INFO [stdout] (vert.x-eventloop-thread-0) Hello Wildfly from Mutiny Ticker 3 17:27:52,892 INFO [stdout] (vert.x-eventloop-thread-0) Hello Wildfly from Mutiny Ticker 4 ...
-
stop the application
mvn wildfly:undeploy
Conclusion
With the latest Wildfly 28 it has become quite straightforward to use Mutiny and MicroProfile Reactive Messaging. It is not provided yet in the standard configurations, but it takes only two steps:
- enable the two Wildfly extensions/subsystems for Reactive Messaging
- the Mutiny dependency in
pom.xml
must not use scopeprovided
There is a minimal example project that shows how to target Wildfly with an application using Mutiny.