[Idea] Azure Storage Queues transport — Paramore.Brighter.MessagingGateway.AzureStorageQueues #4191
xbizzybone
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Summary
Brighter has first-class Azure support through Azure Service Bus, but there is currently no transport for Azure Storage Queues (the
Azure.Storage.QueuesSDK). We'd like to propose — and contribute and maintain — a newParamore.Brighter.MessagingGateway.AzureStorageQueuespackage, plus an optional companionParamore.Brighter.Outbox.AzureTableStorage.We've been running a production command/queue pipeline on Azure Storage Queues + Table Storage for some time (visibility-delay scheduling,
{queue}-poisondead-lettering, dequeue-count tracking), and we'd love to fold that experience back into Brighter rather than keep it in a private fork.Why Storage Queues (and why it complements Service Bus)
Azure Storage Queues sit in a different niche from Service Bus and are a very common starting point on Azure:
<queue>-poisonconvention.How it maps onto Brighter's model
The encouraging part is that Storage Queues fit Brighter's existing abstractions without bending them — and they fit the same shape as the AWS SQS gateway, which is the strongest precedent here because SQS, like Storage Queues, has no broker-native dead-letter and relies on visibility-timeout + a managed poison queue.
Proposed consumer mapping (
IAmAMessageConsumerSync/IAmAMessageConsumerAsync):Receive(timeout)ReceiveMessages(maxMessages, visibilityTimeout)maxMessages←Subscription.BufferSize; stashPopReceipt+DequeueCountinMessageHeader.Bag(same idea as SQSReceiptHandle/ ASBLockToken).Acknowledge(message)DeleteMessage(messageId, popReceipt)Requeue(message, delay)UpdateMessage(messageId, popReceipt, visibilityTimeout: delay)ChangeMessageVisibility). Returns a refreshedPopReceipt.Nack(message)UpdateMessage(..., visibilityTimeout: 0)Reject(message, reason){queue}-poison(or invalid-message queue), then delete originalSqsMessageConsumer.Producer mapping (
IAmAMessageProducerSync/IAmAMessageProducerAsync):Send(message)SendMessage(body)SendWithDelay(message, delay)SendMessage(body, visibilityTimeout: delay)(initial-visibility delay, up to 7 days)Dead-letter & poison handling would reuse the existing role interfaces rather than invent anything:
IUseBrighterDeadLetterSupportandIUseBrighterInvalidMessageSupporton the subscription (asSqsSubscriptiondoes).RejectionReason.DeliveryError→ dead-letter channel andRejectionReason.Unacceptable→ invalid-message channel.{queue}-poison— which is already the Azure Functions convention, so it will feel native to Azure users.DequeueCountto drive the "max receives → reject to poison" policy alongsideSubscription.RequeueCount.Proposed scope
Package 1 —
Paramore.Brighter.MessagingGateway.AzureStorageQueues(its own assembly, per the "one transport, one assembly" rule):AzureStorageQueueMessageProducer/…ProducerFactory(IAmAMessageProducerSync+…Async)AzureStorageQueueMessageConsumer/…ConsumerFactory(IAmAMessageConsumerSync+…Async)AzureStorageQueueChannelFactory(IAmAChannelFactory)AzureStorageQueueSubscription/…Subscription<T>andAzureStorageQueuePublication/…Publication<T>ClientProviderfamily: connection string,DefaultAzureCredential, and Managed Identity viaAzure.Identity.OnMissingChannel.Create/Validate/Assumesupport (create queue +{queue}-poisonwhen asked).Package 2 (optional companion) —
Paramore.Brighter.Outbox.AzureTableStorage:IAmAnOutboxSync<Message, …>+IAmAnOutboxAsync<Message, …>, following the non-relational outbox precedent (DynamoDB / MongoDB / Firestore) rather than the relational stores.Aligning with project conventions
We've read
CONTRIBUTING.md,TESTING_GUIDE.mdand thedocs/transports/pages, and intend to follow them to the letter:docs/adr/template) covering the consumer semantics above — especially the requeue/visibility and poison-queue decisions — so the design is agreed before code. A draft ADR outline is appended below.Nullableenabled,#region Licenceheaders, conventional commits, PRs targetingmaster.Azure.Storage.Queues(andAzure.Data.Tablesfor the outbox) toDirectory.Packages.props;Azure.Identityis already there.docker-compose-azurite.yaml(no cloud account needed in CI). Wired into the Liquid test-generator config if you'd like consistency with the other providers.docs/transports/and a README transport-list entry, plus asamples/Task Queue example to match the RMQ/Kafka samples.Open questions for the maintainers
A few decisions are genuinely yours to steer, and we'd like to align before writing code:
UpdateMessagevisibility (SQS-style, our preference) vs. re-send + delete (ASB-style). Any preference for consistency across the codebase?IUseBrighterDeadLetterSupportpattern with a{queue}-poisondefault, rather than anything bespoke.Azure.Storage.Queuesv12 is stable, so we don't anticipate needing the side-by-side.V4-style split used for the AWS SDK. Agreed?AzureStorageQueues(plural, matching theAzure.Storage.QueuesSDK) vs.AzureStorageQueue. We lean plural; your call.What we'd bring
{queue}-poisondead-lettering, and dequeue-count-driven retry policy.Thanks for Brighter — it's a genuinely well-designed library, and we'd be glad to help round out its Azure coverage. Happy to jump on a call or hash out the ADR in whatever way is easiest for the team.
Appendix — Draft ADR outline (to open once the idea has traction)
Paramore.Brighter.MessagingGateway.AzureStorageQueuesimplementing the standard producer/consumer/channel-factory contracts.Acknowledge → DeleteMessage,Requeue/Nack → UpdateMessage(visibilityTimeout),SendWithDelay → SendMessage(visibilityTimeout).IUseBrighterDeadLetterSupport/IUseBrighterInvalidMessageSupport, defaulting the poison channel to{queue}-poison; drive the policy from nativeDequeueCount+Subscription.RequeueCount.PopReceipt+DequeueCountinMessageHeader.Bag.DefaultAzureCredential/ Managed Identity, mirroring the ASB client-provider family.Azure.Storage.Queues(centrally managed); integration tests rely on Azurite, so CI needs no Azure account.Paramore.Brighter.Outbox.AzureTableStorageon the non-relational outbox precedent (DynamoDB/MongoDB/Firestore).Beta Was this translation helpful? Give feedback.
All reactions