Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/NATS.Client.Services/NatsSvcConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,16 @@ public NatsSvcConfig(string name, string version)
public Dictionary<string, string>? Metadata { get; init; }

/// <summary>
/// Queue group name. (default: "q")
/// Queue group name. (default: "q" unless <see cref="UseQueueGroup"/> is set to <c>false</c>)
/// </summary>
public string QueueGroup { get; init; } = "q";

/// <summary>
/// Use a queue group when creating service subscriptions. (default: <c>true</c>)
/// Queue group name is defined using <see cref="QueueGroup"/> property.
/// </summary>
public bool UseQueueGroup { get; init; } = true;

/// <summary>
/// Stats handler. JSON object returned by this handler will be included in
/// the service stats <c>data</c> property.
Expand Down
4 changes: 2 additions & 2 deletions src/NATS.Client.Services/NatsSvcServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public async ValueTask StopAsync(CancellationToken cancellationToken = default)
/// </remarks>
public ValueTask AddEndpointAsync<T>(Func<NatsSvcMsg<T>, ValueTask> handler, string? name = default, string? subject = default, string? queueGroup = default, IDictionary<string, string>? metadata = default, INatsDeserialize<T>? serializer = default, CancellationToken cancellationToken = default)
{
queueGroup ??= _config.QueueGroup;
queueGroup ??= _config.UseQueueGroup ? _config.QueueGroup : null;
return AddEndpointInternalAsync<T>(handler, name, subject, queueGroup, metadata, serializer, cancellationToken);
}

Expand Down Expand Up @@ -331,7 +331,7 @@ public ValueTask AddEndpointAsync<T>(Func<NatsSvcMsg<T>, ValueTask> handler, str
{
subject ??= name;
var epSubject = subject != null ? $"{GroupName}{_dot}{subject}" : null;
queueGroup ??= QueueGroup ?? _server._config.QueueGroup;
queueGroup ??= QueueGroup ?? (_server._config.UseQueueGroup ? _server._config.QueueGroup : null);
serializer ??= _server._nats.Opts.SerializerRegistry.GetDeserializer<T>();
return _server.AddEndpointInternalAsync(handler, name, epSubject, queueGroup, metadata, serializer, cancellationToken);
}
Expand Down
96 changes: 96 additions & 0 deletions tests/NATS.Client.Services.Tests/ServicesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -383,4 +383,100 @@ public async Task Service_ids_unique()

Assert.Equal(ids.Count, uniqueIds.Count);
}

[Fact]
public async Task Service_without_queue_group()
{
await using var server = await NatsServer.StartAsync();
var (nats1, proxy) = server.CreateProxiedClientConnection();
await using var nats = nats1;
var svc = new NatsSvcContext(nats);

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(60));
var cancellationToken = cts.Token;

// Default with a queue group
{
await using var s1 = await svc.AddServiceAsync(
new NatsSvcConfig("s1", "1.0.0"), // default is { UseQueueGroup = true },
cancellationToken: cancellationToken);

await using var s2 = await svc.AddServiceAsync(
new NatsSvcConfig("s2", "1.0.0"), // default is { UseQueueGroup = true },
cancellationToken: cancellationToken);

await s1.AddEndpointAsync<string>(
name: "ep1",
handler: async m =>
{
await m.ReplyAsync("x", cancellationToken: cancellationToken);
},
cancellationToken: cancellationToken);

await s2.AddEndpointAsync<string>(
name: "ep1",
handler: async m =>
{
await m.ReplyAsync("x", cancellationToken: cancellationToken);
},
cancellationToken: cancellationToken);

await Retry.Until("subscribed", () => proxy.Frames.Count(f => f.Message.StartsWith("SUB ep1")) == 2);

var sub = proxy.Frames.First(f => f.Message.StartsWith("SUB ep1")).Message;
Assert.Matches(@"SUB ep1 q \d+", sub);

var count = 0;
await foreach (var msg in nats.RequestManyAsync<string, string>("ep1", "x", cancellationToken: cancellationToken))
{
count++;
}

// Only one reply because of the queue group
Assert.Equal(1, count);
}

await proxy.FlushFramesAsync(nats);

// Without any queue group
{
await using var s1 = await svc.AddServiceAsync(
new NatsSvcConfig("s1", "1.0.0") { UseQueueGroup = false },
cancellationToken: cancellationToken);

await using var s2 = await svc.AddServiceAsync(
new NatsSvcConfig("s2", "1.0.0") { UseQueueGroup = false },
cancellationToken: cancellationToken);

await s1.AddEndpointAsync<string>(
name: "ep1",
handler: async m =>
{
await m.ReplyAsync("x", cancellationToken: cancellationToken);
},
cancellationToken: cancellationToken);

await s2.AddEndpointAsync<string>(
name: "ep1",
handler: async m =>
{
await m.ReplyAsync("x", cancellationToken: cancellationToken);
},
cancellationToken: cancellationToken);

await Retry.Until("subscribed", () => proxy.Frames.Count(f => f.Message.StartsWith("SUB ep1")) == 2);

var sub = proxy.Frames.First(f => f.Message.StartsWith("SUB ep1")).Message;
Assert.Matches(@"SUB ep1 \d+", sub);

var count = 0;
await foreach (var msg in nats.RequestManyAsync<string, string>("ep1", "x", cancellationToken: cancellationToken))
{
count++;
}

// Two replies because of the absence of any queue groups
Assert.Equal(2, count);
}
}
}