Messaging Made Easy - Practical Demos with NATS and .NET
I just discovered that NATS has released a new NuGet package. The previous NATS.Client
has been iterated and replaced by NATS.NET
. Out of curiosity, I decided to take a closer look and revisit NATS along the way.
NATS.Client
has not been deprecated; it mainly supports older versions of .NET development environments. Back in 2023, I created a demo project using NATS.Client
as a learning resource. If you're interested, you can check out the project via this link.
Here’s the link to the code used for demonstration.
What is NATS
NATS is a messaging system, often referred to as a message broker or messaging agent.
As always, the official team’s definition of the tool is more accurate than mine. Below is the explanation of NATS provided by the official team.
NATS is a lightweight and high-performance messaging system designed for asynchronous communication among different software components, with modern clustering, security and persistence streaming support out of the box, in a single compact binary with no dependencies, available for any modern platform, enabling a vast variety of deployment options from edge, IoT, Kubernetes to bare-metal.
NATS Concepts
NATS is a subject-based messaging system. If you’ve worked with publish-subscribe messaging systems or services before, getting started with NATS should feel quite intuitive.
To illustrate, let’s consider the diagram above. A NATS environment requires at least three components: a publisher (producer), a NATS server, and a subscriber (consumer).
- The subscriber usually represents the client side, while the publisher represents the producer.
- If the client wants to receive messages from the producer, it must first subscribe to the producer's subject to establish a listening channel.
What subject
mean?
Think of a subject as the anchor point for communication. The ratio between subscribers and publishers is often 1:N
or N:N
. If a subscriber wants to receive the correct messages, it needs to rely on subject to ensure it’s listening to the right channel. Conversely, a subscriber can unsubscribe from a subject at any time to stop receiving messages related to it.
I hope this explanation simplifies the concept of NATS. If you’d like to learn more or have any questions, I recommend checking out the official documentation for a deeper understanding.
What are we going to build?
Next, we will simulate a demo scenario for NATS.
We will create two objects: receiver (subscriber)
and sender (publisher)
, and then explore and experiment with some of NATS’s core features step by step.
In a .NET environment, we will experience NATS's five core functionalities:
- Pub-Sub
- Wildcards
- Queue Group (Load Balancing)
- Request-Response
- Streaming
The demo project code can be found above, and you can download it directly.
Prerequisites: Initializing the NATS Server and Project
Before getting started, you need to download the latest version of the NATS Server. It’s extremely lightweight, around 10MB, and won’t take up much space on your computer.
Steps to Set Up NATS Server:
- Visit the official GitHub repository to find the latest release.
- Choose the version that matches your OS and download it.
- After downloading, extract the file to any directory you prefer.
The NATS Server is an executable program with an .exe
extension. Simply double-click to start it.
Important Note:
For the demo project, the NATS Server must remain online throughout the process. Otherwise, the programs in the demonstration will encounter errors.
Optionally, you can attach a .conf
or .json
file to configure your NATS Server. However, this demo focuses on simplicity and will not cover advanced configurations. If you’re interested, refer to this documentation.
Setting Up Subscriber and Publisher:
You’ll need to create two separate console applications: one for the subscriber and one for the publisher. Use the following commands to create them quickly:
The Receiver
The Sender
Core NATS
Now, we’re finally diving into the main content. This section will be divided into two parts: Core Features and JetStream Features.
Let’s start with the core functionalities of NATS. Simply put, these are the foundational features of NATS that revolve around subject-based publish/subscribe messaging.
Without further ado, let’s get started!
Pub/Sub
Let’s begin with the Sender. This Sender will send messages to all subscribers who are subscribed to the subject nats.demo.pubsub
, as illustrated below:
Here, the Subscriber will subscribe to the "nats.demo.pubsub" subject. Whenever any messages related to this subject are published by the Publisher, the Subscriber will receive those messages.
See, it’s that simple!
You can define the types of messages that are returned, for example:
You can run multiple Subscribers simultaneously to see how they all receive the same messages from the Publisher. This is one of the core features of the Pub-Sub model in NATS: when a message is published to a subject, all subscribers listening to that subject receive the same message independently.
Wildcard
NATS provides two types of wildcards
that enable hierarchical subject structures. The hierarchical approach improves the efficiency of message delivery and subject usage.
The Wildcards in NATS
1.*
(Single-Level Wildcard)
This wildcard matches any single level in the subject hierarchy.
Example:
If you subscribe to nats.demo.*
, the following subjects will match:
nats.demo.pubsub1
nats.demo.pubsub2
Subject that won’t match:
nats.demo.pubsub1.xxx
nats.demo.pubsub2.xxx
If the Publisher publishes a message to nats.demo.*
, subscribers who are subscribed to either nats.demo.pubsub1
or nats.demo.pubsub2
will receive the message.
2.>
(Multi-Level Wildcard)
The >
wildcard matches any number of levels in the subject hierarchy.
Example:
If you subscribe to nats.demo.>
, the following subjects will match:
nats.demo.p1.p11.p111
nats.demo.p2.p22
Subject that won’t match:
nats.hello.xxxx
hello.demo.xxxx
As long as the subscriber subscribes to a subject starting with nats.demo
, they will receive messages from any publisher publishing to that hierarchical structure, regardless of how many levels follow.
Summary:
- The
*
wildcard allows matching a single level of the subject hierarchy. - The
>
wildcard allows matching multiple levels in the hierarchy.
Queue Groups
NATS provides an additional feature called Queues
which allows subscribers to register themselves as part of a queue. Subscribers in the same queue group share the responsibility of processing messages. When messages are published to a subject, they are randomly distributed among the subscribers in the queue group, enabling load balancing for response handling.
Here’s how you can implement a simple load balancing scenario where multiple subscribers (workers) share the workload for a subject:
When the Publisher sends ten messages, due to the use of a Queue Group, the messages are randomly distributed among the subscribers.
As a result, the number of messages each subscriber receives might not be equal. This is expected behavior because Queue Groups ensure that messages are handled by a single subscriber within the group, with messages being "pulled" by subscribers as they are available.
Request-Response
In modern distributed systems, the request-response pattern is a common approach. After sending a request, the application either waits for a response within a certain timeout or asynchronously receives the response.
Here’s how you can set up a Publisher to send a request and a Subscriber to respond:
Subscribe to the Request-Reply pattern subject.
To simulate the request-reply scenario, I added a 1-second delay to the subscriber's response time, making it easier to observe the request-reply flow. From the image above, we can clearly see that the publisher receives the message from the subscriber one second after publishing the message.
This form of communication is suitable for building systems that require immediate responses or actions that need confirmation, results, or data.
JetStream
JetStream is an extension of NATS that provides advanced message persistence and management capabilities for distributed systems, making it suitable for scenarios that require reliability, replayability, and persistence.
For example, if the subscriber experiences some downtime and is unable to receive messages, JetStream will store the event and record the sent messages. When the subscriber recovers, it can replay the missed messages.
Since data needs to be stored, it will occupy physical space on the hardware. You might be concerned about space usage, but JetStream allows you to define the maximum allowed storage space, making the space usage manageable.
Now that we've introduced it, let's discuss how to start it.
Starting JetStream is simple; you just need to add the -js
flag to the nats-server
command.
For example, if your nats-server.exe
is located at C:/Download/nats-server.exe
, you can use the following command to start NATS with JetStream functionality enabled:
Code Snippets
The output is actually not much different from other pub-sub subject events. What makes JetStream special is its persistent message storage and replay capabilities, which are particularly valuable during downtime.
From the code, we can see the syntax for acknowledgment (ack). If the subscriber fails to notify the producer that the message has been received, JetStream will log the event and replay the missed message when the subscriber responds.
Ending
This chapter is limited in scope and cannot fully demonstrate all of NATS' features.
NATS has many other excellent extensions and advanced configurations, and I highly recommend visiting the official website to explore more.