NATS Logo by Example

Intro in Services Framework

NATS services have always been straightforward to write. However, with the services framework, the NATS client library further simplifies the building, discovery and monitoring of services. The framework automatically places all subscriptions in a queue group and provides functionality for building subject hierarchies and their handlers.

Without any additional effort, the library enables automatic service discovery and status reporting. The NATS CLI nats micro command provides a simple way to query and report all the services using this framework.

CLI Go Python JavaScript Rust C# C#2 Java Ruby Elixir Crystal C
Jump to the output or the recording
$ nbe run services/intro/dotnet
View the source code or learn how to run this example yourself

Code

using System;
using System.Text;
using System.Threading.Tasks;
using NATS.Client;
using NATS.Client.Service;


string natsUrl = Environment.GetEnvironmentVariable("NATS_URL");
if (natsUrl == null)
{
    natsUrl = "nats://127.0.0.1:4222";
}

Create a new connection factory to create a connection.

Options opts = ConnectionFactory.GetDefaultOptions();
opts.Url = natsUrl;

Creates a connection to nats server at the natsUrl An IConnection is IDisposable so it can be use within using statement.

ConnectionFactory cf = new ConnectionFactory();
using (IConnection c = cf.CreateConnection(opts))
{

What is a Service?

A “service” consists of one or more endpoints. An endpoint can be part of a group of endpoints or by itself.

Defining a Group

In this example, the services will be part of a group. The group name will be the prefix for the subject of the request. Alternatively you could manually specify the group’s subject Here we create the group.

    Group serviceGroup = new Group("minmax");

Defining Endpoints

For each endpoint we give it a name. Like group, you could manually specify the endpoint’s subject. In this example we are adding the endpoint to the group we defined and are providing a ServiceEventHandler implementation

    ServiceEndpoint min = ServiceEndpoint.Builder()
        .WithEndpointName("min")
        .WithGroup(serviceGroup)
        .WithHandler((s, a) => minRequestHandler(c, a.Message))
        .Build();


    ServiceEndpoint max = ServiceEndpoint.Builder()
        .WithEndpointName("max")
        .WithGroup(serviceGroup)
        .WithHandler((s, a) => maxRequestHandler(c, a.Message))
        .Build();

Defining the Service

The Service definition requires a name and version, description is optional. The name must be a simple name consisting of the characters A-Z, a-z, 0-9, dash (-) or underscore (_). Add the endpoints that were created. Give the service a connection to run on. A unique id is created for the service to identify it from different instances of the service.

    Service service = Service.Builder()
        .WithName("minmax")
        .WithVersion("0.0.1")
        .WithDescription("Returns the min/max number in a request")
        .AddServiceEndpoint(min)
        .AddServiceEndpoint(max)
        .WithConnection(c)
        .Build();


    Console.WriteLine("Created Service: " + service.Name + " with the id: " + service.Id);

Running the Service

To run the service we call service.StartService(). Uou can have a taks that returns when service.Stop() is called.

    Task<bool> serviceStopFuture = service.StartService();

For the example we use a simple string for the input and output but in the real world it will be some sort of formatted data such as json. The input and output is sent as the data payload of the NATS message.

    byte[] input = Encoding.UTF8.GetBytes("-1,2,100,-2000");

To “call” a service, we simply make a request to the proper endpoint with data that it expects. Notice how the group name is prepended to the endpoint name.

    Msg minMsg = c.Request("minmax.min", input);
    Console.WriteLine($"Min value is: {Encoding.UTF8.GetString(minMsg.Data)}");


    Msg maxMsg = c.Request("minmax.max", input);
    Console.WriteLine($"Max value is: {Encoding.UTF8.GetString(maxMsg.Data)}");

The statistics being managed by micro should now reflect the call made to each endpoint, and we didn’t have to write any code to manage that.

    EndpointStats esMin = service.GetEndpointStats(min.Name);
    Console.WriteLine($"The min service received {esMin.NumRequests} request(s).");


    EndpointStats esMax = service.GetEndpointStats(max.Name);
    Console.WriteLine($"The max service received {esMax.NumRequests} request(s).");
}


static void minRequestHandler(IConnection conn, ServiceMsg msg)
{
    int min = int.MaxValue;
    string[] input = Encoding.UTF8.GetString(msg.Data).Split(',');
    foreach (string n in input) {
        min = Math.Min(min, int.Parse(n));
    }
    msg.Respond(conn, Encoding.UTF8.GetBytes("" + min));
}


static void maxRequestHandler(IConnection conn, ServiceMsg msg)
{
    int max = int.MinValue;
    string[] input = Encoding.UTF8.GetString(msg.Data).Split(',');
    foreach (string n in input) {
        max = Math.Max(max, int.Parse(n));
    }
    msg.Respond(conn, Encoding.UTF8.GetBytes("" + max));
}

Output

Created Service: minmax with the id: Y0Z6t-Hg8tLMBTi3_YmSRK
Min value is: -2000
Max value is: 100
The min service received 1 request(s).
The max service received 1 request(s).
DisconnectedEvent, Connection: 5
ClosedEvent, Connection: 5

Recording

Note, playback is half speed to make it a bit easier to follow.