Skip to main content

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.

Writing a Verticle (Base Code) »/application/tutorials/writing-a-verticle/
IntegerMessage.java
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 {}
MessageTransformer.java
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:

IntegerMessage.java
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:

MessageTransformer.java
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():

MessageTransformer.java
// [...]

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:

MessageTransformer.java
// [...]

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/