Site icon Vinsguru

NATS Messaging In Java

Overview:

In this tutorial, I would like to introduce NATS Messaging to you & talk about its advantages in Microservices Architecture and some code samples in Java.

NATS Messaging Server:

We had discussed a lot about inter microservices communication using Kafka, gRPC, RSocket, even Redis in this blog. They are all great and they have their own pros and cons. For ex: Kafka has very good throughput – but the infrastructure set up is quite complex! Redis setup is easy compared to Kafka but it is an in-memory DB, does not have the partitions/replications as Kafka does.

NATS is an open source messaging system. It is a very simple, scalable messaging server for cloud native applications with its own pros and cons.

Pros:

Cons:

NATS Messaging Server:

docker run -p 4222:4222 nats:alpine
<dependency>
    <groupId>io.nats</groupId>
    <artifactId>jnats</artifactId>
    <version>2.6.8</version>
</dependency>
// nats connection
// it connects to nats://localhost:4222 by default
Connection nats = Nats.connect();

// remote server
Connection nats = Nats.connect("nats://10.11.12.13:4222");

Features:

NATS has 3 basic features.

Pub/Sub:

Pub/Sub is a messaging pattern in which senders/publishers send the messages to a channel/topic instead of sending to a specific receiver. In fact, the publisher does not have any idea who the receiver is! The interested parties (aka receivers/subscribers) will subscribe to the channels, receive the message and process them. It is an one-to-many communication also known as fan-out communication. (Image courtesy – nats.io site)

Subscriber:

// connect to nats server
Connection nats = Nats.connect();

// message dispatcher
Dispatcher dispatcher = nats.createDispatcher(msg -> {});

// subscribes to nats.demo.service channel
dispatcher.subscribe("nats.demo.service", msg -> {
    System.out.println("Received : " + new String(msg.getData()));
});

Here we subscribe to nats.demo.service which is the name for our topic. The msg.getData() returns the passed object in byte[] format!

Publisher:

// connect to nats server
Connection nats = Nats.connect();

// publish a message to the channel
nats.publish("nats.demo.service", "Hello NATS".getBytes());

I could see this output in the subscriber console.

Received : Hello NATS

Wildcards Support:

A single subscriber can listen to multiple channels.

dispatcher.subscribe("nats.*.service", msg -> {
    System.out.println("Received : " + new String(msg.getData()));
});

For example, in the above example, the subscriber can receive messages from below channels if there are any etc.

The * matches single token in the address. Not the entire string/address.

For ex: nats.* will match any channels starting with nats.<some-token>. but not nats.<some-token>.<some-other-token>

If we need to match any channel/topic starting with nats, we need to use nats.> which will match anything starts with nats.

Subscriber Channel Format Channel Names Match?
foo.* foo.bar Yes
foo.* foo.bar.service No
foo.*.service foo.bar.service Yes
foo.*.service foo.bar No
foo.> foo.bar Yes
foo.> foo.bar.service Yes
foo.*.> foo.bar.service Yes

Request/Reply:

The above pub/sub is more of a fire-and-forget mode of communication. Sometimes we expect a response back for the request we send.

// subscribes to nats.demo.service channel
dispatcher.subscribe("nats.demo.service", msg -> {
    System.out.println("Received : " + new String(msg.getData()));
    nats.publish(msg.getReplyTo(), "Hello Publisher!".getBytes());
});

Check the above subscriber. It can respond back to the sender by using the sender’s address. Imagine this as 2 persons communicating via email. When the subscriber/receiver receives the message, it simply replies to the message.

reply-to address would be null for the fire-and-forget mode communication.

The publisher side code would be like this. Note that the publisher requests for response by using the request method. (We can also pass the Duration for timeout as additional parameter.)

nats.request("nats.demo.service", "Hello NATS".getBytes())
        .thenApply(Message::getData)  // gets executed when we get response from receiver
        .thenApply(String::new)
        .thenAccept(s -> System.out.println("Response from Receiver: " + s));

Subscriber output:

Received : Hello NATS

Publisher output:

Response from Receiver: Hello Publisher!

Queue Group:

In a typical pub/sub messaging pattern, every single subscriber will be notified and receiving the messages. Sometimes, we might want just 1 single subscriber to process the message. Not all. We do not want redundant processing.
For ex: Lets consider an audit-service which is responsible for logging the audit events.

// subscriber 1
dispatcher.subscribe("audit.event.service", "audit-service", msg -> {
    System.out.println("Received 1 : " + new String(msg.getData()));
});

// subscriber 2
dispatcher.subscribe("audit.event.service", "audit-service", msg -> {
    System.out.println("Received 2 : " + new String(msg.getData()));
});

When we use group name and NATS sees multiple subscribers to this channel, it will automatically distribute the incoming messages. There is no change in the publisher side. Publisher does not have to know anything about the subscribers. It does not matter if it is 1 subscriber or group of subscribers. It will simply publish as usual in the fire-and-forget mode.

nats.publish("audit.event.service", "an order placed".getBytes());

Unsubscribe:

The subscribers can unsubscribe anytime or after processing specific number of messages.

Subscription subscription = dispatcher.subscribe("nats.demo.service", msg -> {
    System.out.println("Received : " + new String(msg.getData()));
});
dispatcher.unsubscribe(subscription, 10);

In this example, the subscriber is interested in processing only 10 messages. It does not get any more messages after that.

NATS Messaging In Microservices:

The above NATS Messaging server functionalities can be used for Microservices architecture.

Summary:

With these examples above, we can clearly see NATS Messaging server is very simple to use. As it is 7 MB in size and starts in no time, It is very easy to scale for the modern cloud native applications. It can even run in IoT devices. We can also run multiple instances of NATS Messaging servers in the clustering mode for high availability.

Learn more about NATS.

Happy learning 🙂

Share This:

Exit mobile version