Skip to content

Commit 3083db5

Browse files
authored
Add other client extensions (#637)
* Add client extension methods and refactor interfaces Refactor to use interfaces for enhanced flexibility and testability across various components like NatsJSContext and NatsConnection. Added new extension methods to easily create contexts for Object Store, Key-Value Store, and Services on NATS client and connection instances. * Add public `Context` properties to store interfaces Updated INatsObjStore, INatsKVContext, and INatsSvcContext interfaces to include public `Context` properties. This change ensures consistent access to the underlying context objects across various components. * dotnet format * Refactor context creation to use JetStream directly * Make extensions namespace NATS.Net * Removed debug print * Rename to JetStreamContext * Fix inheritdoc * Fix build warnings * Fix build warnings and add test * Fix test
1 parent d5f28f7 commit 3083db5

28 files changed

+321
-286
lines changed

sandbox/Example.Client/Program.cs

+19
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
using System.Text;
44
using NATS.Client.JetStream;
5+
using NATS.Client.KeyValueStore;
6+
using NATS.Client.ObjectStore;
7+
using NATS.Client.Services;
58
using NATS.Net;
69

710
CancellationTokenSource cts = new();
@@ -95,6 +98,22 @@
9598
Console.WriteLine($"JetStream Stream: {stream.Info.Config.Name}");
9699
}
97100

101+
// Use KeyValueStore by referencing NATS.Client.KeyValueStore package
102+
var kv1 = client.CreateKeyValueStoreContext();
103+
var kv2 = js.CreateKeyValueStoreContext();
104+
await kv1.CreateStoreAsync("store1");
105+
await kv2.CreateStoreAsync("store1");
106+
107+
// Use ObjectStore by referencing NATS.Client.ObjectStore package
108+
var obj1 = client.CreateObjectStoreContext();
109+
var obj2 = js.CreateObjectStoreContext();
110+
await obj1.CreateObjectStoreAsync("store1");
111+
await obj2.CreateObjectStoreAsync("store1");
112+
113+
// Use Services by referencing NATS.Client.Services package
114+
var svc = client.CreateServicesContext();
115+
await svc.AddServiceAsync("service1", "1.0.0");
116+
98117
await cts.CancelAsync();
99118

100119
await Task.WhenAll(tasks);

src/NATS.Client.JetStream/INatsJSContext.cs

+27
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ public interface INatsJSContext
1111
/// </summary>
1212
INatsConnection Connection { get; }
1313

14+
/// <summary>
15+
/// Provides configuration options for the JetStream context.
16+
/// </summary>
17+
NatsJSOpts Opts { get; }
18+
1419
/// <summary>
1520
/// Creates new ordered consumer.
1621
/// </summary>
@@ -296,4 +301,26 @@ ValueTask<NatsJSPublishConcurrentFuture> PublishConcurrentAsync<T>(
296301
NatsJSPubOpts? opts = default,
297302
NatsHeaders? headers = default,
298303
CancellationToken cancellationToken = default);
304+
305+
/// <summary>
306+
/// Generates a new base inbox string using the connection's inbox prefix.
307+
/// </summary>
308+
/// <returns>A new inbox string.</returns>
309+
string NewBaseInbox();
310+
311+
/// <summary>
312+
/// Sends a request message to a JetStream subject and waits for a response.
313+
/// </summary>
314+
/// <param name="subject">The JetStream API subject to send the request to.</param>
315+
/// <param name="request">The request message object.</param>
316+
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the API call.</param>
317+
/// <typeparam name="TRequest">The type of the request message.</typeparam>
318+
/// <typeparam name="TResponse">The type of the response message.</typeparam>
319+
/// <returns>A task representing the asynchronous operation, with a result of type <typeparamref name="TResponse"/>.</returns>
320+
ValueTask<TResponse> JSRequestResponseAsync<TRequest, TResponse>(
321+
string subject,
322+
TRequest? request,
323+
CancellationToken cancellationToken = default)
324+
where TRequest : class
325+
where TResponse : class;
299326
}

src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ internal class NatsJSOrderedPushConsumer<T>
4545
{
4646
private readonly ILogger _logger;
4747
private readonly bool _debug;
48-
private readonly NatsJSContext _context;
48+
private readonly INatsJSContext _context;
4949
private readonly string _stream;
5050
private readonly string _filter;
5151
private readonly INatsDeserialize<T> _serializer;
@@ -68,7 +68,7 @@ internal class NatsJSOrderedPushConsumer<T>
6868
private int _done;
6969

7070
public NatsJSOrderedPushConsumer(
71-
NatsJSContext context,
71+
INatsJSContext context,
7272
string stream,
7373
string filter,
7474
INatsDeserialize<T> serializer,
@@ -417,23 +417,23 @@ private void CreateSub(string origin)
417417

418418
internal class NatsJSOrderedPushConsumerSub<T> : NatsSubBase
419419
{
420-
private readonly NatsJSContext _context;
420+
private readonly INatsJSContext _context;
421421
private readonly CancellationToken _cancellationToken;
422422
private readonly INatsConnection _nats;
423423
private readonly NatsHeaderParser _headerParser;
424424
private readonly INatsDeserialize<T> _serializer;
425425
private readonly ChannelWriter<NatsJSOrderedPushConsumerMsg<T>> _commands;
426426

427427
public NatsJSOrderedPushConsumerSub(
428-
NatsJSContext context,
428+
INatsJSContext context,
429429
Channel<NatsJSOrderedPushConsumerMsg<T>> commandChannel,
430430
INatsDeserialize<T> serializer,
431431
NatsSubOpts? opts,
432432
CancellationToken cancellationToken)
433433
: base(
434434
connection: context.Connection,
435435
manager: context.Connection.SubscriptionManager,
436-
subject: context.NewInbox(),
436+
subject: context.NewBaseInbox(),
437437
queueGroup: default,
438438
opts)
439439
{
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
11
using NATS.Client.Core;
2+
using NATS.Client.JetStream;
23

3-
namespace NATS.Client.JetStream;
4+
// ReSharper disable once CheckNamespace
5+
namespace NATS.Net;
46

57
public static class NatsClientExtensions
68
{
9+
/// <summary>
10+
/// Creates a JetStream context using the provided NATS client.
11+
/// </summary>
12+
/// <param name="client">The NATS client used to create the JetStream context.</param>
13+
/// <returns>Returns an instance of <see cref="INatsJSContext"/> for interacting with JetStream.</returns>
714
public static INatsJSContext CreateJetStreamContext(this INatsClient client)
815
=> CreateJetStreamContext(client.Connection);
916

17+
/// <summary>
18+
/// Creates a JetStream context using the provided NATS connection.
19+
/// </summary>
20+
/// <param name="connection">The NATS connection used to create the JetStream context.</param>
21+
/// <returns>Returns an instance of <see cref="INatsJSContext"/> for interacting with JetStream.</returns>
1022
public static INatsJSContext CreateJetStreamContext(this INatsConnection connection)
1123
=> new NatsJSContext(connection);
1224
}

src/NATS.Client.JetStream/NatsJSConsumer.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ internal async ValueTask<NatsJSConsume<T>> ConsumeInternalAsync<T>(INatsDeserial
284284

285285
opts ??= new NatsJSConsumeOpts();
286286
serializer ??= _context.Connection.Opts.SerializerRegistry.GetDeserializer<T>();
287-
var inbox = _context.NewInbox();
287+
var inbox = _context.NewBaseInbox();
288288

289289
var max = NatsJSOptsDefaults.SetMax(opts.MaxMsgs, opts.MaxBytes, opts.ThresholdMsgs, opts.ThresholdBytes);
290290
var timeouts = NatsJSOptsDefaults.SetTimeouts(opts.Expires, opts.IdleHeartbeat);
@@ -332,7 +332,7 @@ internal async ValueTask<NatsJSOrderedConsume<T>> OrderedConsumeInternalAsync<T>
332332
ThrowIfDeleted();
333333

334334
serializer ??= _context.Connection.Opts.SerializerRegistry.GetDeserializer<T>();
335-
var inbox = _context.NewInbox();
335+
var inbox = _context.NewBaseInbox();
336336

337337
var max = NatsJSOptsDefaults.SetMax(opts.MaxMsgs, opts.MaxBytes, opts.ThresholdMsgs, opts.ThresholdBytes);
338338
var timeouts = NatsJSOptsDefaults.SetTimeouts(opts.Expires, opts.IdleHeartbeat);
@@ -382,7 +382,7 @@ internal async ValueTask<NatsJSFetch<T>> FetchInternalAsync<T>(
382382
ThrowIfDeleted();
383383
serializer ??= _context.Connection.Opts.SerializerRegistry.GetDeserializer<T>();
384384

385-
var inbox = _context.NewInbox();
385+
var inbox = _context.NewBaseInbox();
386386

387387
var max = NatsJSOptsDefaults.SetMax(opts.MaxMsgs, opts.MaxBytes);
388388
var timeouts = NatsJSOptsDefaults.SetTimeouts(opts.Expires, opts.IdleHeartbeat);

src/NATS.Client.JetStream/NatsJSContext.cs

+18-15
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ public NatsJSContext(INatsConnection connection, NatsJSOpts opts)
3232

3333
public INatsConnection Connection { get; }
3434

35-
internal NatsJSOpts Opts { get; }
35+
/// <inheritdoc />
36+
public NatsJSOpts Opts { get; }
3637

3738
/// <summary>
3839
/// Calls JetStream Account Info API.
@@ -238,6 +239,22 @@ public async ValueTask<NatsJSPublishConcurrentFuture> PublishConcurrentAsync<T>(
238239
return new NatsJSPublishConcurrentFuture(sub);
239240
}
240241

242+
/// <inheritdoc />
243+
public string NewBaseInbox() => NatsConnection.NewInbox(Connection.Opts.InboxPrefix);
244+
245+
/// <inheritdoc />
246+
public async ValueTask<TResponse> JSRequestResponseAsync<TRequest, TResponse>(
247+
string subject,
248+
TRequest? request,
249+
CancellationToken cancellationToken = default)
250+
where TRequest : class
251+
where TResponse : class
252+
{
253+
var response = await JSRequestAsync<TRequest, TResponse>(subject, request, cancellationToken);
254+
response.EnsureSuccess();
255+
return response.Response!;
256+
}
257+
241258
internal static void ThrowIfInvalidStreamName([NotNull] string? name, [CallerArgumentExpression("name")] string? paramName = null)
242259
{
243260
#if NETSTANDARD
@@ -262,20 +279,6 @@ internal static void ThrowIfInvalidStreamName([NotNull] string? name, [CallerArg
262279
}
263280
}
264281

265-
internal string NewInbox() => NatsConnection.NewInbox(Connection.Opts.InboxPrefix);
266-
267-
internal async ValueTask<TResponse> JSRequestResponseAsync<TRequest, TResponse>(
268-
string subject,
269-
TRequest? request,
270-
CancellationToken cancellationToken = default)
271-
where TRequest : class
272-
where TResponse : class
273-
{
274-
var response = await JSRequestAsync<TRequest, TResponse>(subject, request, cancellationToken);
275-
response.EnsureSuccess();
276-
return response.Response!;
277-
}
278-
279282
internal async ValueTask<NatsJSResponse<TResponse>> JSRequestAsync<TRequest, TResponse>(
280283
string subject,
281284
TRequest? request,

src/NATS.Client.JetStream/NatsJSMsg.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,11 @@ public interface INatsJSMsg<out T>
140140
/// <typeparam name="T">User message type</typeparam>
141141
public readonly struct NatsJSMsg<T> : INatsJSMsg<T>
142142
{
143-
private readonly NatsJSContext _context;
143+
private readonly INatsJSContext _context;
144144
private readonly NatsMsg<T> _msg;
145145
private readonly Lazy<NatsJSMsgMetadata?> _replyToDateTimeAndSeq;
146146

147-
public NatsJSMsg(NatsMsg<T> msg, NatsJSContext context)
147+
public NatsJSMsg(NatsMsg<T> msg, INatsJSContext context)
148148
{
149149
_msg = msg;
150150
_context = context;

src/NATS.Client.KeyValueStore/INatsKVContext.cs

+5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ namespace NATS.Client.KeyValueStore;
44

55
public interface INatsKVContext
66
{
7+
/// <summary>
8+
/// Provides access to the JetStream context associated with the Key-Value Store operations.
9+
/// </summary>
10+
INatsJSContext JetStreamContext { get; }
11+
712
/// <summary>
813
/// Create a new Key Value Store or get an existing one
914
/// </summary>

src/NATS.Client.KeyValueStore/INatsKVStore.cs

+6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
using NATS.Client.Core;
2+
using NATS.Client.JetStream;
23

34
namespace NATS.Client.KeyValueStore;
45

56
public interface INatsKVStore
67
{
8+
/// <summary>
9+
/// Provides access to the JetStream context associated with the Object Store operations.
10+
/// </summary>
11+
INatsJSContext JetStreamContext { get; }
12+
713
/// <summary>
814
/// Name of the Key Value Store bucket
915
/// </summary>

src/NATS.Client.KeyValueStore/Internal/NatsKVWatchSub.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,23 @@ namespace NATS.Client.KeyValueStore.Internal;
77

88
internal class NatsKVWatchSub<T> : NatsSubBase
99
{
10-
private readonly NatsJSContext _context;
10+
private readonly INatsJSContext _context;
1111
private readonly CancellationToken _cancellationToken;
1212
private readonly INatsConnection _nats;
1313
private readonly NatsHeaderParser _headerParser;
1414
private readonly INatsDeserialize<T> _serializer;
1515
private readonly ChannelWriter<NatsKVWatchCommandMsg<T>> _commands;
1616

1717
public NatsKVWatchSub(
18-
NatsJSContext context,
18+
INatsJSContext context,
1919
Channel<NatsKVWatchCommandMsg<T>> commandChannel,
2020
INatsDeserialize<T> serializer,
2121
NatsSubOpts? opts,
2222
CancellationToken cancellationToken)
2323
: base(
2424
connection: context.Connection,
2525
manager: context.Connection.SubscriptionManager,
26-
subject: context.NewInbox(),
26+
subject: context.NewBaseInbox(),
2727
queueGroup: default,
2828
opts)
2929
{

src/NATS.Client.KeyValueStore/Internal/NatsKVWatcher.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ internal sealed class NatsKVWatcher<T> : IAsyncDisposable
2828
{
2929
private readonly ILogger _logger;
3030
private readonly bool _debug;
31-
private readonly NatsJSContext _context;
31+
private readonly INatsJSContext _context;
3232
private readonly string _bucket;
3333
private readonly INatsDeserialize<T> _serializer;
3434
private readonly NatsKVWatchOpts _opts;
@@ -53,7 +53,7 @@ internal sealed class NatsKVWatcher<T> : IAsyncDisposable
5353
private INatsJSConsumer? _initialConsumer;
5454

5555
public NatsKVWatcher(
56-
NatsJSContext context,
56+
INatsJSContext context,
5757
string bucket,
5858
IEnumerable<string> keys,
5959
INatsDeserialize<T> serializer,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using NATS.Client.Core;
2+
using NATS.Client.JetStream;
3+
using NATS.Client.KeyValueStore;
4+
5+
// ReSharper disable once CheckNamespace
6+
namespace NATS.Net;
7+
8+
public static class NatsClientExtensions
9+
{
10+
/// <summary>
11+
/// Creates a NATS Key-Value Store context using the specified NATS client.
12+
/// </summary>
13+
/// <param name="client">The NATS client instance.</param>
14+
/// <returns>An instance of <see cref="INatsKVContext"/> which can be used to interact with the Key-Value Store.</returns>
15+
public static INatsKVContext CreateKeyValueStoreContext(this INatsClient client)
16+
=> CreateKeyValueStoreContext(client.CreateJetStreamContext());
17+
18+
/// <summary>
19+
/// Creates a NATS Key-Value Store context using the specified NATS connection.
20+
/// </summary>
21+
/// <param name="connection">The NATS connection instance.</param>
22+
/// <returns>An instance of <see cref="INatsKVContext"/> which can be used to interact with the Key-Value Store.</returns>
23+
public static INatsKVContext CreateKeyValueStoreContext(this INatsConnection connection)
24+
=> CreateKeyValueStoreContext(connection.CreateJetStreamContext());
25+
26+
/// <summary>
27+
/// Creates a NATS Key-Value Store context using the specified NATS JetStream context.
28+
/// </summary>
29+
/// <param name="context">The NATS JetStream context instance.</param>
30+
/// <returns>An instance of <see cref="INatsKVContext"/> which can be used to interact with the Key-Value Store.</returns>
31+
public static INatsKVContext CreateKeyValueStoreContext(this INatsJSContext context)
32+
=> new NatsKVContext(context);
33+
}

0 commit comments

Comments
 (0)