Using JsonMessage for event bus messages
In this tutorial, you'll extend your basic Verticle by using the JsonMessage
APIs from the telestion-core
packages to get some type safety in event bus
messages.
Prerequisites
To complete this tutorial, you should be familiar with Records in Java and how to write basic Verticles.
What you'll build
You'll extend the code from
Writing a Verticle with the
JsonMessage
APIs. This stops you from having to guess about the kind of
message sent and poses much fewer edge cases you may need to handle.
It's also highly compatible with various event bus bridges, for example allowing to exchange these messages with web clients.
When not to use JsonMessage
It sometimes makes sense to drop type safety and instead handle "raw" JSON. In
these cases, you should use the raw Jackson JSON APIs (like JsonObject
) and
send serialized forms via the event bus (to ensure compatibility with event bus
bridges).
One case where this might make sense is when you handle arbitrary input data that doesn't necessarily have fixed types.
package de.wuespace.telestion.project.example;
import com.fasterxml.jackson.annotation.JsonProperty;
import de.wuespace.telestion.api.message.JsonMessage;
record IntegerMessage(
@JsonProperty int value
) implements JsonMessage {}
package de.wuespace.telestion.project.example;
import de.wuespace.telestion.api.verticle.NoConfiguration;
import de.wuespace.telestion.api.verticle.TelestionVerticle;
public class MessageTransformer extends TelestionVerticle<NoConfiguration> {
public static void main(String[] args) throws InterruptedException {
// [...]
vertx.deployVerticle(new MessageTransformer()).onSuccess(res -> {
// publish a number once the Verticle is deployed
vertx.eventBus().publish("input", new IntegerMessage(3).json());
});
}
@Override
public void onStart() {
var eb = getVertx().eventBus();
eb.consumer("input", event -> {
JsonMessage.on(IntegerMessage.class, event, message -> {
int received = message.value();
var output = new IntegerMessage(received * 2);
eb.publish("output", output.json());
});
});
}
}
Step 1: Creating the record
The first thing you need to do is to create the message container, a record
that contains your message data.
In this case, you'll just expand upon your "integer processor" and create a record that holds one integer value.
Create a file IntegerMessage.java
in the
de.wuespace.telestion.project.example
package and add the following code:
package de.wuespace.telestion.project.example;
import com.fasterxml.jackson.annotation.JsonProperty;
import de.wuespace.telestion.api.message.JsonMessage;
record IntegerMessage(
@JsonProperty int value
) implements JsonMessage {}
The two special ingredients to make this a JsonMessage
are:
- declaring that it implements the
de.wuespace.telestion.api.message.JsonMessage
interface - marking its attribute(/-s) as JSON properties using the
@JsonProperty
decorator
Now, you can begin using you IntegerMessage
with the Telestion APIs.
Step 2: Listening for messages of that class
Since you now have a proper JsonMessage
wrapper, you can drop the previous
"casting to int
" from the raw Object message.body()
in your consumer and
can, instead, use one of Telestion's convenience functions for filtering for
valid messages.
Replace your previous eb.consumer()
logic in MessageTransformer::onStart()
with this code:
package de.wuespace.telestion.project.example;
// [...]
public class MessageTransformer extends TelestionVerticle<NoConfiguration> {
// [...]
@Override
public void onStart() {
// TODO: use vertx instead of getVertx
var eb = getVertx().eventBus();
eb.consumer("input", event -> {
JsonMessage.on(IntegerMessage.class, event, message -> {
int received = message.value();
eb.publish("output", received * 2);
});
});
}
}
Instead of handling the event
directly, you pass it into the
JsonMessage.on()
function. This then filters messages and calls a handler
passed into it if and only if the message type matches the specified class
(here: IntegerMessage
).
The first argument to the handler, message
, then is the already deserialized
instance of IntegerMessage
and you can, thus, use it to further process the
data.
In this case, you use message.value()
to retrieve the value
field within the
record. Since you already know that it's of the type IntegerMessage
where
value
is an integer, you no longer need to cast any types or handle other edge
cases.
Step 3: Publishing messages
Now that you can receive IntegerMessage
messages, you also need to publish
them.
Create a new IntegerMessage
instance called output
and instead of publishing
received * 2
, publish output.json()
:
// [...]
public class MessageTransformer extends TelestionVerticle<NoConfiguration> {
// [...]
@Override
public void onStart() {
var eb = getVertx().eventBus();
eb.consumer("input", event -> {
JsonMessage.on(IntegerMessage.class, event, message -> {
int received = message.value();
var output = new IntegerMessage(received * 2);
eb.publish("output", output.json());
});
});
}
}
Calling .json()
To use the JsonMessage
APIs when publishing a message, you must publish the
result of calling .json()
on your JsonMessage
. This converts its contents to
a JSON String
that can get parsed on the receiving end.
Now, all you need to do to test your code is to also publish an IntegerMessage
in the main()
method:
// [...]
public class MessageTransformer extends TelestionVerticle<NoConfiguration> {
public static void main(String[] args) throws InterruptedException {
// [...]
vertx.deployVerticle(new MessageTransformer()).onSuccess(res -> {
// publish a number once the Verticle is deployed
vertx.eventBus().publish("input", new IntegerMessage(3).json());
});
}
// [...]
}
When you now re-run your main()
method, you should get the following output:
Output: {"value":6,"className":"de.wuespace.telestion.project.example.IntegerMessage"}
className
attribute
As you can see, JsonMessage
instances automatically encode a className
attribute into the encoded JSON. This allows the Telestion helper functions to
verify and automatically re-instantiate it with the correct class type.
Next steps
With that, you've used your first actual Telestion API. And you've gotten away from nasty, unsafe type casts.
In the next tutorial, you'll learn how to add configuration options to your Verticle. After that, you're already equipped with the knowledge to understand almost all real Verticles in Telestion projects.
Adding configuration options »/application/tutorials/adding-configuration-options/