When you work on distributed cloud platforms, the topic of an internal communication arises. By communication we understand architecture decisions that ties up various level of your systems; for instance, your service’s components need to be loosely coupled in order to ensure a higher degree of an autonomy. In my practical experience with Vertx, I defined an importance of the following principle: instead of layered components that are tied together with the dependency injection container, it is wiser to separate levels to various Vertx units (verticles) and organize a fluid communication between them. Let me be clear: my goal is to encourage to try Eventbus-based messaging between components.
An architecture of the Event Bus
Before we will continue with practical concerns, it seems wise to talk about theoretical foundations of the EventBus pattern (you can also find another term – “Message Bus”). This is due to the fact, that this technique is not only connected with Vertx, yet, it goes far and this principle is used among most event-driven systems. The main idea behind this pattern is to be able deliver messages (sometimes called “events”, however it is not 100% correct; we will use “messages”) asynchronously via a common component, called “broker”. In this case, the whole system is considered loosely coupled, because different parts can be connected or disconnected as needed without the necessity to change the whole system. Components communicate with each other using various messaging protocols, such as publish-subscribe, point-to-point, request-response etc. We will observe those are available in Vertx 4 later in this post.
In its most basic way, we can define the event bus to have following parts: publishers, subscribers, messages. This implementation is presented on the graph below:
The mentioned structure can be considered similar to the Observer design pattern, yet only to a certain degree. Within that pattern, the Observable component has to hold references to Subscribers in order to notify them on new events. This fact makes the whole system strong coupled, as requires more steps in order to add or remove components. From the other side, the EventBus, that acts as a intermediate actor, solves this issue, because instead of of registering with Observables, Subscribers can register with the EventBus. This also hides Subscribers from Observables and vice versa, because now, components communicate using messages, that delivered to the address. This address can belong to a single (one-to-one messaging) or to many recipients (broadcasting).
So, that is the simple event bus architecture. In next several subsections we will have a deeper overview of its components.
The publisher is known as a component, that send messages to an event bus. The difference with an Observable in this situation is following: no need to contain references to Subscribers; an allowance of broadcasting (sending messages in both directions in the many-to-many fashion); a single component can have a functionality of both Subscriber and Observable in very simple way.
When it comes to the particular implementation in Vertx 4, we can find following methods of the
EventBus class, that allows to implement a publishing functionality:
publish= this is an implementation of the publish-subscribe messaging pattern
send= this is an implementation of point-to-point messaging pattern
request= this is an implementation of request-response messaging pattern
Don’t worry, as we will cover these methods and underlaying principles in more details, when we will come to practical aspects. As for now, we need to remember, that publishing techniques are not limited to a single approach; as well, it is allowed to deliver messages to a single or to many consumers. Moreover, we talk about the logical functionality, however the particular component in your system can implement both publisher and subscriber behavior. That leads to another aspect – how that is implemented in reality. Unlike some other frameworks, Vertx 4 does not force you to implement interfaces, rather the implementation is organized around the
EventBus component in two ways:
- Publishing directly using the EventBus instance with one of aforesaid methods
- Using the
MessageProducer, which is basically a stream, that allows to write messages in
Your implementation depends on your needs, as Vertx does not force developers to stick with the particular approach.
The subscriber is essentially a receiver, that listen for messages and is able to process them. The last part of the definition is important. The Subscriber in the EventBus architecture has differences with Subscriber in the Observer pattern, namely it does not rely to the Observable directly, but has a particular address, where messages are delivered by a bus. It is crucial to note here, that even in the case of point-to-point communication (single Subscriber and single Obseervable), components are not tied directly. You need to remember, that based on its implementation, EventBus in Vertx will deliver a message to a single destination, but it does not mean that you will have a single destination in your system.
In Vertx, subscribers are called consumers and are represented by the
MessageConsumer interface. There are two types of consumers in Vertx:
- Ordinary consumer = the address of this consumer is propagated across the cluster
- Local consumer = the address of this consumer is not propagated across the cluster
In both situations, we utilize the
MessageConsumer, which is from the technical point of view, a stream where messages are written to and from where it is possible to read them by receiver components.
The message component is what is delivered using the EventBus. Unlike the Observer pattern, where both sides are explicitly aware about the type of data sent/received, with the EventBus its type is not forced, so it is important to ensure about compatibility. However, it is worth to mention, that the message is not limited just to data itself. Typically it contains other useful information, such as errors, replying destinations, headers.
In Vertx, the
Message class acts as a container, that contains a body, headers, origin and destination addresses, as well error information. This class is also used for replying by call of the
reply method. It is also possible to explicitly break the delivery and to notify the sender using the
fail method (which acts similar to Bad Request response).
Please note, that by default, the EventBus class transports data as a basic
Object class. You need to register a custom codec if you would like to send your own entity classes via the Event Bus and to specify it in using the
MessageConsumer class. This step is outside the scope of this post; you can refer to my Principles of Vertx ebook in order to get practical information about it.
The last component which is observed in the theoretical part of this post, is a bus itself. The event bus acts as an intermediate component between publishers and subscribers. This is similar to an event broker topology pattern – the bus acts as a broker in this case. In Vertx, the Eventbus is related with the Vertx instance, in a sense, that it is a single bus for a single Vertx instance. In order to obtain a reference for the EventBus use the following code:
Vertx vertx = Vertx.vertx(); EventBus bus = vertx.eventBus();
From a technical perspective, in order to use the event bus messaging capabilities you have to go through these steps:
- Register a publisher and a receiver
- Send a message from a sender
- Receive a message inside a receiver
- Un register handlers (Optionally, yet recommended in big systems)
This is the end of the theoretical part. Now, we can continue exploring practical implementations of messaging protocols, available with the Vertx 4 EventBus.
Vertx 4 messaging patterns
When we talk about message driven systems (event driven systems), we consider ones, that use different asynchronous messaging protocols in order to communicate between components. This is also refereed to message channels. From the technical point of view, the selection of the particular implementation depends on a set of factors, such as:
- The number of senders and receivers, e.g. do you need to have an one-to-one, an one-to-many or a many-to-many communication
- Is the message flow is one-directional or bi-directional (that means, it is supposed to be a reply for the message, that should be delivered back to the sender)
These aspects are important to consider from the beginning to choose the right type of messaging protocol for your system. Next, we observe each of them.
The publish-subscribe messaging pattern is the most straightforward. It is commonly used to implement a broadcasting (delivery of message from a single publisher to many consumers). The Vertx Eventbus ensures that the message will be broadcasted to all subscribers, that hold the same address. The idea is presented on the graph below:
In Vertx, the
publish method is used to deliver the message to all registered consumers. Take a look on the code snippet:
Vertx vertx = Vertx.vertx(); EventBus eventBus = vertx.eventBus(); JsonObject payload = new JsonObject().put("message", "Hello world"); eventBus.publish("my-receiver", payload);
On the counterpart, in order to receive the message you need to perform following steps:
Vertx vertx = Vertx.vertx(); EventBus eventBus = vertx.eventBus(); MessageConsumer<JsonObject> consumer = eventBus.consumer("my-receiver"); consumer .handler(message -> System.out.println(message.body())) .exceptionHandler(error -> error.printStackTrace());
Please note, that the handler registration (with the
handler method) is required in order to make the consumer registered within the event bus. Also, the consumer logic is similar to all messaging protocols, so we will omit it in remained subsections.
As it was mentioned already, the broadcasting (publish-subscribe pattern) is a delivery of messages from a single publisher to many consumers. However, you face cases, when you need to perform a transportation between a single publisher and a single consumer. That is called the point-to-point messaging pattern. Please refer to the diagram below:
Being a software architect for a long time, and especially working with Vertx 3 and then 4, I would like to add here an important note, that usually is missed by developers. Point-to-point messaging, implemented with the Vertx, means that the event bus will ensure that a message will be delivered to a single consumer. Yet, it does not mean, that it ensures that there is only single consumer in the system. For sure, that is a developer’s duty to aware, that the destination has a unique recipient, otherwise it will be deliver it to the first using the round-robin algorithm.
Vertx vertx = Vertx.vertx(); EventBus eventBus = vertx.eventBus(); JsonObject payload = new JsonObject().put("message", "Hello world"); eventBus.send("my-receiver", payload);
Like I said, the receiver part is same for all patterns, so please refer to the previous subsection.
The final message channel, that we will observe in this post is the request-response pattern. This is a special case of the previous technique. The difference is that a consumer can send back an answer (response) for the message (request) and the publisher should guarantee to receive it. In Vertx it is performed by specifying a reply handler. The general idea is demonstrated below:
In the code, it can implemented as following:
Vertx vertx = Vertx.vertx(); EventBus eventBus = vertx.eventBus(); JsonObject payload = new JsonObject().put("message", "Hello world"); eventbus.request("my-receiver", payload, result -> System.out.println(result.result().body()));
The consumer part of this pipeline is similar to what we already did, but we can provide a reply in addition to it. That is implemented on the
Message object, not on a consumer, like here:
Vertx vertx = Vertx.vertx(); EventBus eventBus = vertx.eventBus(); MessageConsumer<JsonObject> consumer = eventBus.consumer("my-receiver"); consumer .handler(message -> message.reply("Everything is ok")) .exceptionHandler(error -> error.printStackTrace());
The EventBus is considered as a “nervous system” of Vertx, because it allows to implement event-driven apps. Such architecture is a very good way to ensure loosely coupling of components and to organize an asynchronous communication using various message patterns. In this post we observed all three message patterns (or channels) available with the Vertx 4 EventBus – publish-subscribe (broadcasting), point-to-point and request-response. Many additional aspects although were not covered because they are out of scope, such as error handling, how to deal with invalid delivery (dead messages collection) as well some advanced topics (clustering, bridges) etc. We will cover them in their turn. I hope, that this post was helpful for you and motivated you to explore such wonderful piece of software (masterpiece – I would say) as Vertx. In case of questions, please contact me.