BrighterCommand / BrighterCommand/Brighter
ASB: queue creation ignores SubscriptionConfiguration, so OnMissingChannel.Create cannot make a session-enabled queue
- Dominant language
- C#
- Stars
- 2.5k
- Forks
- 296
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 21
Description
## Summary
On the Azure Service Bus transport, the **queue** creation path ignores `AzureServiceBusSubscriptionConfiguration` almost entirely, while the **topic subscription** path honours it. The practical effect is that `OnMissingChannel.Create` cannot produce a session-enabled queue, or one with a non-default lock duration, TTL, or dead-lettering-on-expiration — even when the subscription explicitly asks for them.
This matters more than a normal missing-option gap because `RequiresSession` is **immutable after queue creation** in Azure Service Bus. If Brighter auto-creates the queue without it, the only remedy is deleting and recreating the entity.
## Current behaviour
`AdministrationClientWrapper.CreateQueueAsync` takes three parameters and sets two properties:
```csharp
// AzureServiceBusWrappers/AdministrationClientWrapper.cs:69
public async Task CreateQueueAsync(string queueName, TimeSpan? autoDeleteOnIdle = null, long? maxMessageSizeInKilobytes = default)
{
...
await _administrationClient.CreateQueueAsync(new CreateQueueOptions(queueName)
{
AutoDeleteOnIdle = autoDeleteOnIdle ?? TimeSpan.MaxValue,
MaxMessageSizeInKilobytes = maxMessageSizeInKilobytes
});
```
Compare the subscription path in the same class, which threads the configuration through in full:
```csharp
// AzureServiceBusWrappers/AdministrationClientWrapper.cs:106-113
var subscriptionOptions = new CreateSubscriptionOptions(topicName, subscriptionName)
{
MaxDeliveryCount = subscriptionConfiguration.MaxDeliveryCount,
DeadLetteringOnMessageExpiration = subscriptionConfiguration.DeadLetteringOnMessageExpiration,
LockDuration = subscriptionConfiguration.LockDuration,
DefaultMessageTimeToLive = subscriptionConfiguration.DefaultMessageTimeToLive,
AutoDeleteOnIdle = subscriptionConfiguration.QueueIdleBeforeDelete,
RequiresSession = subscriptionConfiguration.RequireSession
};
```
The consumer already holds the configuration it would need, and passes only the idle timeout:
```csharp
// AzureServiceBusQueueConsumer.cs:107
await AdministrationClientWrapper.CreateQueueAsync(Topic, SubscriptionConfiguration.QueueIdleBeforeDelete);
```
So a subscription declaring `RequireSession = true` with `UseServiceBusQueue = true` gets a consumer that attempts session receive against a queue Brighter just created **without** sessions. A `SessionId` stamped on a message bound for a non-session queue is silently ignored by the broker — no error, no ordering, and nothing observable until you notice messages aren't being processed in the order you asked for.
## Impact
- `RequireSession` is unreachable via `OnMissingChannel.Create` for queues, and unfixable afterwards without deleting the entity.
- `LockDuration`, `DefaultMessageTimeToLive` and `DeadLetteringOnMessageExpiration` are all settable on `AzureServiceBusSubscriptionConfiguration` and all silently dropped for queues, so auto-created queues get broker defaults regardless of what the subscription declares.
- Consumers wanting an ordered, session-keyed queue have to provision the entity out-of-band (IaC, or a bespoke startup hosted service holding a `ServiceBusAdministrationClient`) purely to set one immutable boolean.
## Proposed change
Thread `AzureServiceBusSubscriptionConfiguration` through to `CreateQueueOptions` on the queue path, mirroring the subscription path. No new configuration surface is needed — `RequireSession`, `LockDuration`, `DefaultMessageTimeToLive` and `DeadLetteringOnMessageExpiration` already exist on that type.
## One wrinkle worth deciding on
There are two callers of `CreateQueueAsync`, and only one of them has a `SubscriptionConfiguration`:
```csharp
// AzureServiceBusQueueMessageProducer.cs:80
await _administrationClientWrapper.CreateQueueAsync(channelName);
```
A producer has no subscription configuration, so for a queue used by both a producer and a consumer in the same application, whichever starts first creates the entity — and if that's the producer, the queue is created with defaults and `RequiresSession` is then permanently wrong. Options that seem plausible from the outside:
- Have the producer's create path be `Validate`-like for session-requiring queues, so the consumer's configured create wins.
- Carry the relevant queue settings on the publication as well, so both callers can create an equivalent entity.
- Leave the producer path as-is and document that a session-enabled queue must be created by the consumer or out-of-band.
Happy to take direction on which of those you'd prefer before opening a PR — I'm glad to do the work either way.
## Version
Reproduced against `Paramore.Brighter.MessagingGateway.AzureServiceBus` **10.7.0**; the signature is unchanged on `master` at time of writing:
```
M:Paramore.Brighter.MessagingGateway.AzureServiceBus.AzureServiceBusWrappers.IAdministrationClientWrapper.CreateQueueAsync(System.String,System.Nullable{System.TimeSpan},System.Nullable{System.Int64})
```
Contributor guide
Assessment
This issue has not been assessed yet.