Azure Service Bus transport

BabelQueue.AzureServiceBus is an Azure Service Bus transport on the .NET core. It sends the canonical envelope as the Body with the §4 native projection, and consumes by routing each message to a handler by URN — so a message it produces is consumed by any other BabelQueue SDK, and vice-versa.

Install

dotnet add package BabelQueue.AzureServiceBus

Requirements: .NET 8. It pulls BabelQueue.Core and Azure.Messaging.ServiceBus transitively.

Produce

using Azure.Messaging.ServiceBus;
using BabelQueue.AzureServiceBus;

await using var client = new ServiceBusClient("<connection-string>"); // or a namespace + TokenCredential

var sender = client.CreateSender("orders");
var id = await new AsbPublisher(sender)
    .PublishAsync("urn:babel:orders:created", new Dictionary<string, object?> { ["order_id"] = 1042 });

PublishAsync returns the message meta.id; pass a traceId to continue a trace, or a delay (TimeSpan) to schedule native delayed delivery (ScheduledEnqueueTime).

Consume

var receiver = client.CreateReceiver("orders"); // PeekLock
var handlers = new Dictionary<string, BabelHandler>
{
    ["urn:babel:orders:created"] = (envelope, message, ct) =>
    {
        // envelope.Data, envelope.TraceId, envelope.Attempts ...
        return Task.CompletedTask;
    },
};
var consumer = new AsbConsumer(receiver, handlers, new AsbConsumerOptions
{
    OnError = (err, env, msg) => Console.Error.WriteLine(err),
});
await consumer.RunAsync(cancellationToken);

A throwing handler Abandons the message — the broker redelivers it and increments DeliveryCount (at-least-once); at MaxDeliveryCount it auto-moves to the native dead-letter sub-queue. Auth is a connection string or the fully-qualified namespace + a TokenCredential (DefaultAzureCredential).

Contract mapping (§4)

EnvelopeAzure Service Bus
bodyBody (byte-identical across SDKs)
job (URN)Subject
trace_idCorrelationId
meta.idMessageId
meta.schema_versionApplicationProperties["bq-schema-version"]
meta.langApplicationProperties["bq-source-lang"]
meta.created_atApplicationProperties["bq-created-at"] (ms)
attemptsmax(body, DeliveryCount − 1)
reserve / ack / retryPeekLock → Complete / Abandon

ServiceBusSender / ServiceBusReceiver are mockable, so the unit tests use Moq + ServiceBusModelFactory — no Azure, no network. The envelope is unchanged (schema_version stays 1); Azure Service Bus is purely additive.