Simplifying Distributed Systems with NATS and Ballerina
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
- Download and install Ballerina.
- Download and install NATS server.
- Download and install any code editor.
- Basic knowledge of NATS core concepts.
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.
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.
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