Simplifying Distributed Systems with NATS and Ballerina

arshika✨
Ballerina Swan Lake Tech Blog
5 min readJan 23, 2024

This article was written using Ballerina Swan Lake Update 8.0 (2201.8.0)

Distributed systems have become an integral part of modern application development. As applications grow in complexity, communicating between various services and components in a reliable and efficient manner is crucial. One technology stack that can help simplify this challenge is the combination of NATS and Ballerina.

Understanding NATS

NATS is a high-performance, open-source messaging system that acts as a central nervous system for connecting applications and services. It is designed for simplicity, performance, and reliability, making it an excellent choice for building distributed and scalable systems. NATS offers features like publish-subscribe, request-reply, and message queuing, making it a versatile solution for various communication patterns.

Ballerina: The Integration-Focused Language

Ballerina is a language designed specifically for building integrations. It comes with built-in support for writing micro-services and connecting them to various systems. It’s simplicity, expressiveness, and strong typing make it an excellent choice for building micro-services, APIs, and integration solutions.

Setting Up the Environment

Using NATS with Ballerina

The Ballerina language provides a dedicated library, ballerinax/nats, to seamlessly integrate NATS into your applications. Here’s how you can get started with using NATS in Ballerina:

Step 1: Import the Library

In your Ballerina code, import the ballerinax/nats package.

import ballerinax/nats;

Step 2: Create a NATS Connection

Initialize a connection to your NATS server. You can configure the connection by specifying the URL of your NATS server, among other options.

nats:Client newClient = check new (nats:DEFAULT_URL);
// OR
nats:Client newClient = check new ("nats://localhost:4222");
// Replace with your NATS server URL

Step 3: Publish a Message

You can simply publish a message to a NATS subject as follows.

import ballerinax/nats;

public function main() returns error? {

nats:Client orderClient = check new (nats:DEFAULT_URL);

// Produces a message to the specified subject.
check orderClient->publishMessage({
content: "Hello World".toBytes(),
subject: "hello.new"
});
}

Step 4: Subscribe to a Subject

To subscribe to a NATS subject and receive messages asynchronously, create a new nats.Listener and attach a nats.Service as follows. You can attach as many services to a single listener as you want.

import ballerina/log;
import ballerinax/nats;

// Binds the consumer to listen to the messages published to the 'hello.new' subject.
service "hello.new" on new nats:Listener(nats:DEFAULT_URL) {

remote function onMessage(nats:BytesMessage message) returns error? {
string stringMessage = check string:fromBytes(message.content);
log:printInfo(string `Received message: ${stringMessage}`);
}
}

This example demonstrates the basic usage of NATS with Ballerina. However, you can easily adapt and extend it to fit your specific use case, including handling different message types, implementing error handling, and connecting to secure NATS servers.

Real-Life Use Case: Order Processing Microservices

Let’s consider a common scenario in modern e-commerce applications — order processing. We’ll use Ballerina and NATS to build two microservices, one for order creation and another for order notification. When a new order is created, the order service publishes an event on NATS, and the notification service subscribes to this event to send order notifications.

Order Creation Microservice (Order Service)

import ballerina/http;
import ballerinax/nats;

type Order readonly & record {
int orderId;
string productName;
decimal price;
boolean isValid;
};

service / on new http:Listener(9092) {
private final nats:Client orderClient;

function init() returns error? {
// Initiate the NATS client at the start of the service. This will be used
// throughout the lifetime of the service.
self.orderClient = check new (nats:DEFAULT_URL);
}

resource function post orders(Order newOrder) returns http:Accepted|error {
// Produces a message to the specified subject.
check self.orderClient->publishMessage({
content: newOrder,
subject: "orders.valid"
});

return http:ACCEPTED;
}
}

Order Notification Microservice (Notification Service)

import ballerina/log;
import ballerinax/nats;

public type Order record {
int orderId;
string productName;
decimal price;
boolean isValid;
};

// Binds the consumer to listen to the messages published to the 'orders.valid' subject.
service "orders.valid" on new nats:Listener(nats:DEFAULT_URL) {

function init() returns error? {
log:printInfo("Listening to order notifications.");
}

remote function onMessage(Order 'order) returns error? {
if 'order.isValid {
log:printInfo(string `Received valid order for ${'order.productName}`);
}
}
}

In this example, the Order Service creates orders and publishes a message to the “order.valid” NATS subject. The Notification Service subscribes to this subject, receives the message, and processes order notifications filtering out valid orders.

Compiling and Running

  • If the Order Service and the Notification Service are implemented within two different Ballerina projects, you can simply execute the following command inside each of them in separate terminals.
bal run
  • If the Order Service and the Notification Service are implemented in two .bal files, then run the following command to execute them.
bal run <path_to_the_file>
eg:
bal run order_service.bal
bal run Desktop/notification_service.bal

If the Notification Service and the Order Service started without any issue, following lines will be printed in the terminal.

Notification Service
Order Service

You can invoke the Order Service using the following command.

curl http://localhost:9092/orders -H "Content-type:application/json" -d "{\"orderId\": 1, \"productName\": \"Sport shoe\", \"price\": 27.5, \"isValid\": true}"

If the Order Service was able to send the message successfully, the following line will be printed in the terminal of the Notification Service.

Notification Service

Conclusion

NATS and Ballerina make a powerful combination for building distributed systems, microservices, and integration solutions. With the simplicity of Ballerina and the efficiency of NATS, you can create robust, scalable, and responsive applications that can handle the challenges of modern distributed architectures.

By exploring these technologies further and delving into their more advanced features and configurations, you can take your integration and messaging solutions to the next level and streamline the development of distributed systems.

Check out other cool packages provided by Ballerina in Ballerina Central.

Ballerina is an open-source project. We value your feedback and contributions. Please provide feedback, questions, and issues related to the Ballerina NATS package in Ballerina Library GitHub repository.

References:

Versions used in this article:

  • Ballerina: Ballerina Swan Lake Update 8.0
  • NATS client package: ballerinax/nats:2.10.0

--

--

arshika✨
Ballerina Swan Lake Tech Blog

Senior software engineer @ WSO2 👩‍💻 | Ballerina lang 🩵