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.
Code
using System;
using Microsoft.Extensions.Logging;
using NATS.Client.Core;
using NATS.Client.Serializers.Json;
using NATS.Client.Services;
using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
var logger = loggerFactory.CreateLogger("NATS-by-Example");
NATS_URL
environment variable can be used to pass the locations of the NATS servers.
var url = Environment.GetEnvironmentVariable("NATS_URL") ?? "127.0.0.1:4222";
Connect to NATS server. Since connection is disposable at the end of our scope we should flush our buffers and close connection cleanly.
var opts = new NatsOpts
{
Url = url,
LoggerFactory = loggerFactory,
SerializerRegistry = NatsJsonSerializerRegistry.Default,
Name = "NATS-by-Example",
};
await using var nats = new NatsConnection(opts);
var svc = new NatsSvcContext(nats);
Defining a Service
This will create a service definition. Service definitions are made up of
the service name (which can’t have things like whitespace in it), a version,
and a description. Even with no running endpoints, this service is discoverable
via the micro protocol and by service discovery tools like nats micro
.
All of the default background handlers for discovery, PING, and stats are
started at this point.
var service = await svc.AddServiceAsync(new NatsSvcConfig("minmax", "0.0.1")
{
Description = "Returns the min/max number in a request"
});
Each time we create a service, it will be given a new unique identifier. If multiple
copies of the minmax
service are running across a NATS subject space, then tools
like nats micro
will consider them like unique instances of the one service and the
endpoint subscriptions are queue subscribed, so requests will only be sent to one
endpoint instance at a time.
TODO: service.Info
Adding endpoints
Groups serve as namespaces and are used as a subject prefix when endpoints
don’t supply fixed subjects. In this case, all endpoints will be listening
on a subject that starts with minmax.
var root = await service.AddGroupAsync("minmax");
Adds two endpoints to the service, one for the min
operation and one for
the max
operation. Each endpoint represents a subscription. The supplied handlers
will respond to minmax.min
and minmax.max
, respectively.
await root.AddEndpointAsync(HandleMin, "min", serializer: NatsJsonSerializer<int[]>.Default);
await root.AddEndpointAsync(HandleMax, "max", serializer: NatsJsonSerializer<int[]>.Default);
Make a request of the min
endpoint of the minmax
service, within the minmax
group.
Note that there’s nothing special about this request, it’s just a regular NATS
request.
var min = await nats.RequestAsync<int[], int>("minmax.min", new[]
{
-1, 2, 100, -2000
}, requestSerializer: NatsJsonSerializer<int[]>.Default);
logger.LogInformation("Requested min value, got {Min}", min.Data);
Make a request of the max
endpoint of the minmax
service, within the minmax
group.
var max = await nats.RequestAsync<int[], int>("minmax.max", new[]
{
-1, 2, 100, -2000
}, requestSerializer: NatsJsonSerializer<int[]>.Default);
logger.LogInformation("Requested max value, got {Max}", max.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. TODO: service.Stats
That’s it!
logger.LogInformation("Bye!");
ValueTask HandleMin(NatsSvcMsg<int[]> msg)
{
var min = msg.Data.Min();
return msg.ReplyAsync(min);
}
ValueTask HandleMax(NatsSvcMsg<int[]> msg)
{
var min = msg.Data.Max();
return msg.ReplyAsync(min);
}
Output
info: NATS.Client.Core.NatsConnection[1001] Try to connect NATS nats://nats:4222 info: NATS.Client.Core.Internal.NatsReadProtocolProcessor[1005] Received server info: ServerInfo { Id = NBNKAKMI2JMCWMZGM53FPYI2VYTYKXRKICP6BA37JEKXHKVQPJPKDLYV, Name = NBNKAKMI2JMCWMZGM53FPYI2VYTYKXRKICP6BA37JEKXHKVQPJPKDLYV, Version = 2.10.4, ProtocolVersion = 1, GitCommit = abc47f7, GoVersion = go1.21.3, Host = 0.0.0.0, Port = 4222, HeadersSupported = True, AuthRequired = False, TlsRequired = False, TlsVerify = False, TlsAvailable = False, MaxPayload = 1048576, JetStreamAvailable = True, ClientId = 5, ClientIp = 192.168.240.3, Nonce = , Cluster = , ClusterDynamic = False, ClientConnectUrls = , WebSocketConnectUrls = , LameDuckMode = False } info: NATS.Client.Core.NatsConnection[1001] Connect succeed NATS-by-Example, NATS nats://nats:4222 info: NATS-by-Example[0] Requested min value, got -2000 info: NATS-by-Example[0] Requested max value, got 100 info: NATS-by-Example[0] Bye! info: NATS.Client.Core.NatsConnection[1001] Disposing connection NATS-by-Example
Install NuGet packages
NATS.Net
,NATS.Client.Serializers.Json
andMicrosoft.Extensions.Logging.Console
.