Skip to content

Commit

Permalink
Add opts to client
Browse files Browse the repository at this point in the history
  • Loading branch information
mtmk committed Oct 16, 2024
1 parent abe2d5f commit 729d2c4
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 117 deletions.
24 changes: 16 additions & 8 deletions docs/documentation/advanced/intro.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
# Advanced Options

For more advanced configuration, you can use the [`NatsOpts`](xref:NATS.Client.Core.NatsOpts)
class to configure the connection to the NATS server. You can create a new instance of `NatsOpts` and pass it to the
[`NatsConnection`](xref:NATS.Client.Core.NatsConnection) constructor. `NatsConnection` is the class underlying
[`NatsClient`](xref:NATS.Net.NatsClient) and is responsible for managing the connection to the NATS server.
class to configure the connection to the NATS server.

For example, you can hook your logger to `NatsConnection` to make sure all is working as expected or
For example, you can hook your logger to `NatsClient` to make sure all is working as expected or
to get help diagnosing any issues you might have:

(For this example, you need to add [Microsoft.Extensions.Logging.Console](https://www.nuget.org/packages/Microsoft.Extensions.Logging.Console) from Nuget.)
[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/IntroPage.cs#logging)]

`NatsConnection` also implements [`INatsClient`](xref:NATS.Client.Core.INatsClient) as well as additional
advance APIs, which means you can use it as a `NatsClient` instance without any changes to your code.
[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/IntroPage.cs#logging)]

## NatsClient vs NatsConnection

Expand All @@ -36,9 +32,21 @@ instantiate a `NatsConnection` with default options, you would only have basic s
for `int`, `string`, and `byte[]` types, and you would need to set up the serializers for your data classes
if you want to use e.g., JSON serialization.

The other difference is that `NatsClient` sets `SubPendingChannelFullMode` internal channel option to
`BoundedChannelFullMode.Wait` to avoid dropping messages when the subscriber's internal channel is full.
This is a good default for most cases, but you can change it by setting the `SubPendingChannelFullMode` option
in `NatsClient` constructor.

[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/IntroPage.cs#opts)]

You can also use the `NatsConnection` class directly.

[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/IntroPage.cs#opts2)]

**Which One Should I Use?**

If you are new to NATS, you should use `NatsClient` as it provides a more user-friendly interface.
If you are new to NATS, you should use `NatsClient` as it provides a more user-friendly interface
with sensible defaults especially for serialization.
If you need more control over the connection options, AOT deployments, or custom serializers,
you should use `NatsConnection`.

Expand Down
68 changes: 32 additions & 36 deletions docs/documentation/advanced/serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,58 @@

NATS.Net supports serialization of messages using a simple interface [`INatsSerializer<T>`](xref:NATS.Client.Core.INatsSerializer`1).

By default, the client uses the [`NatsDefaultSerializer<T>`](xref:NATS.Client.Core.NatsDefaultSerializer`1)
which can handle binary data, UTF8 strings and numbers. You can provide your own
By default, the client uses the [`NatsClientDefaultSerializer<T>`](xref:NATS.Net.NatsClientDefaultSerializer`1)
which can handle binary data, UTF8 strings, numbers, and ad hoc JSON serialization. You can provide your own
serializer by implementing the [`INatsSerializer<T>`](xref:NATS.Client.Core.INatsSerializer`1) interface or using the
[`NatsJsonContextSerializer<T>`](xref:NATS.Client.Core.NatsJsonContextSerializer`1) for generated
JSON serialization. Serializers can also be chained together to provide multiple serialization formats typically
depending on the types being used.

## Default Serializer Registry
## Serializer Registries

Default serializer is used when no serializer is provided to the connection options. It can handle binary data, UTF8
strings and numbers. It uses the following rules to determine the type of the data:
There are two default serializer registries that can be used to provide serializers for specific types.
For any other serializers, you can implement your own serializer registry.

### NatsClientDefaultSerializer

This is the default serializer for [`NatsClient`](xref:NATS.Net.NatsClient) that
is used when no serializer registry is provided as an option.

- Can serialize what `NatsDefaultSerializerRegistry` can (see below).
- Additionally, it can serialize data classes using ad hoc JSON serialization.
- Uses reflection to generate serialization code at runtime so it's not AOT friendly.

The default client serializer is designed to be used by developers
who want to have an out-of-the-box experience for basic use cases like sending and receiving UTF8 strings,
or JSON messages.

### NatsDefaultSerializerRegistry

This is the default serializer for [`NatsConnection`](xref:NATS.Client.Core.NatsConnection) that
is used when no serializer registry is provided as an option.
See also [the differences between NatsClient vs NatsConnection](intro.md#natsclient-vs-natsconnection)

- AOT friendly
- If the data is a byte array, [`Memory<byte>`](https://learn.microsoft.com/dotnet/api/system.memory-1), [`IMemoryOwner<byte>`](https://learn.microsoft.com/dotnet/api/system.buffers.imemoryowner-1) or similar it is treated as binary data.
- If the data is a string or similar it is treated as UTF8 string.
- If the data is a primitive (for example `DateTime`, `int` or `double`. See also [`NatsUtf8PrimitivesSerializer<T>`](xref:NATS.Client.Core.NatsUtf8PrimitivesSerializer`1)) it is treated as the primitive encoded as a UTF8 string.
- For any other type, the serializer will throw an exception.

The default connection serializer is designed to be AOT friendly and mostly suitable for binary data.

### Using Custom Serializer Registries

Serialising custom data formats can be done by implementing the serializer registry interface
[`INatsSerializerRegistry`](xref:NATS.Client.Core.INatsSerializerRegistry)
that can be used to provide a custom serializer instances for specific types.

You would be using the default serializer by not specifying a serializer registry in connection options
or by setting it to the default explicitly:

[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs#default)]

The default serializer is designed to be used by developers who want to only work with binary data, and provide an out
of the box experience for basic use cases like sending and receiving UTF8 strings.

## Using JSON Serializer Context

The [`NatsJsonContextSerializer<T>`](xref:NATS.Client.Core.NatsJsonContextSerializer`1) uses the [`System.Text.Json`](https://learn.microsoft.com/dotnet/api/system.text.json) serializer to serialize and deserialize messages. It relies
The [`NatsJsonContextSerializer<T>`](xref:NATS.Client.Core.NatsJsonContextSerializer`1)
uses the [`System.Text.Json`](https://learn.microsoft.com/dotnet/api/system.text.json)
serializer to serialize and deserialize messages. It relies
on the [`System.Text.Json` source generator](https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator/)
to generate the serialization code at compile time. This is the recommended JSON serializer for most use cases and it's
required for [Native AOT deployments](https://learn.microsoft.com/dotnet/core/deploying/native-aot).
Expand Down Expand Up @@ -94,27 +114,3 @@ they can be used to allocate buffers from the [`ArrayPool<T>`](https://learn.mic
buffers for binary data and you want to avoid allocating buffers on for every operation (e.g. `new byte[]`) reducing
garbage collection pressure. They may also be useful for example, if your subscription may receive messages with
different formats and the only way to determine the format is by reading the message.

## Using JSON Serialization with Reflection

If you're not using [Native AOT deployments](https://learn.microsoft.com/dotnet/core/deploying/native-aot) you can use
the [`NatsJsonSerializer<T>`](xref:NATS.Client.Serializers.Json.NatsJsonSerializer`1) to serialize and deserialize
messages. [`NatsJsonSerializer<T>`](xref:NATS.Client.Serializers.Json.NatsJsonSerializer`1) uses [`System.Text.Json`](https://learn.microsoft.com/dotnet/api/system.text.json)
APIs that can work with types that are not registered to generate serialization code.

Using this serializer is most useful for use cases where you want to send and receive JSON messages and you don't want to
worry about registering types. It's also useful for prototyping and testing. To use the serializer you need to install
the [`NATS.Client.Serializers.Json`](https://www.nuget.org/packages/NATS.Client.Serializers.Json) Nuget package.

```shell
$ dotnet add package NATS.Client.Serializers.Json
```

Then set the serializer as the default for the connection:

[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs#using-json)]

[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs#json)]

Now you can use any type without worrying about registering it. [`System.Text.Json`](https://learn.microsoft.com/dotnet/api/system.text.json)
serializer will use reflection to serialize and deserialize the messages.
25 changes: 25 additions & 0 deletions src/NATS.Client.Simplified/NatsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,31 @@ public NatsClient(
Connection = new NatsConnection(opts);
}

/// <summary>
/// Initializes a new instance of the <see cref="NatsClient"/> class.
/// </summary>
/// <param name="opts">NATS client options.</param>
/// <param name="pending">Sets `SubPendingChannelFullMode` option. (default: wait)</param>
/// <remarks>
/// By default, the <paramref name="opts"/> will be merged with the default options
/// overriding SerializationRegistry with <see cref="NatsClientDefaultSerializerRegistry.Default"/>
/// and SubPendingChannelFullMode with <see cref="BoundedChannelFullMode.Wait"/>.
/// </remarks>
public NatsClient(NatsOpts opts, BoundedChannelFullMode pending = BoundedChannelFullMode.Wait)
{
if (ReferenceEquals(opts.SerializerRegistry, NatsOpts.Default.SerializerRegistry))
{
opts = opts with
{
SerializerRegistry = NatsClientDefaultSerializerRegistry.Default,
};
}

opts = opts with { SubPendingChannelFullMode = pending };

Connection = new NatsConnection(opts);
}

/// <inheritdoc />
public INatsConnection Connection { get; }

Expand Down
32 changes: 32 additions & 0 deletions tests/NATS.Client.Simplified.Tests/ClientTest.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using System.Text;
using System.Threading.Channels;
using NATS.Client.Core;
using NATS.Client.Core.Tests;
using NATS.Client.JetStream;
using NATS.Client.JetStream.Models;
using NATS.Client.Serializers.Json;
using NATS.Net;

// ReSharper disable AccessToDisposedClosure
Expand Down Expand Up @@ -208,5 +211,34 @@ await Retry.Until(
await Task.WhenAll(task1, task2, task3, task4, task5, task6);
}

[Fact]
public void Client_opts_default_regitry()
{
var client = new NatsClient(new NatsOpts());
Assert.Equal(NatsClientDefaultSerializerRegistry.Default, client.Connection.Opts.SerializerRegistry);
}

[Fact]
public void Client_opts_custom_registry()
{
var registry = new NatsJsonSerializerRegistry();
var client = new NatsClient(new NatsOpts { SerializerRegistry = registry });
Assert.Equal(registry, client.Connection.Opts.SerializerRegistry);
}

[Fact]
public void Client_opts_default_pending()
{
var client = new NatsClient(new NatsOpts());
Assert.Equal(BoundedChannelFullMode.Wait, client.Connection.Opts.SubPendingChannelFullMode);
}

[Fact]
public void Client_opts_set_pending()
{
var client = new NatsClient(new NatsOpts(), pending: BoundedChannelFullMode.DropNewest);
Assert.Equal(BoundedChannelFullMode.DropNewest, client.Connection.Opts.SubPendingChannelFullMode);
}

private record MyData(int Id, string Name);
}
32 changes: 32 additions & 0 deletions tests/NATS.Net.DocsExamples/Advanced/IntroPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#pragma warning disable IDE0007
#pragma warning disable IDE0008

using System.Threading.Channels;
using Microsoft.Extensions.Logging;
using NATS.Client.Core;

Expand Down Expand Up @@ -69,6 +70,37 @@ public async Task Run()

var opts = new NatsOpts { LoggerFactory = loggerFactory };

await using var nc = new NatsClient(opts);
#endregion
}

{
#region opts

var opts = new NatsOpts
{
// You need to set pending in the constructor and not use
// the option here, as it will be ignored.
SubPendingChannelFullMode = BoundedChannelFullMode.DropOldest,

// Your custom options
SerializerRegistry = new MyProtoBufSerializerRegistry(),

// ...
};

await using var nc = new NatsClient(opts, pending: BoundedChannelFullMode.DropNewest);
#endregion
}

{
#region opts2

var opts = new NatsOpts
{
// Your custom options
};

await using var nc = new NatsConnection(opts);
#endregion
}
Expand Down
12 changes: 6 additions & 6 deletions tests/NATS.Net.DocsExamples/Advanced/SecurityPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public async Task Run()

{
#region user-pass
var opts = NatsOpts.Default with
var opts = new NatsOpts
{
AuthOpts = NatsAuthOpts.Default with
{
Expand All @@ -24,27 +24,27 @@ public async Task Run()
},
};

await using var nats = new NatsConnection(opts);
await using var nats = new NatsClient(opts);
#endregion
}

{
#region tls-implicit
var opts = NatsOpts.Default with
var opts = new NatsOpts
{
TlsOpts = new NatsTlsOpts
{
Mode = TlsMode.Implicit,
},
};

await using var nats = new NatsConnection(opts);
await using var nats = new NatsClient(opts);
#endregion
}

{
#region tls-mutual
var opts = NatsOpts.Default with
var opts = new NatsOpts
{
TlsOpts = new NatsTlsOpts
{
Expand All @@ -54,7 +54,7 @@ public async Task Run()
},
};

await using var nats = new NatsConnection(opts);
await using var nats = new NatsClient(opts);
#endregion
}
}
Expand Down
Loading

0 comments on commit 729d2c4

Please sign in to comment.