Apache ActiveMQ Artemis transport

BabelQueue.Artemis is an Apache ActiveMQ Artemis transport on the .NET core, built on AMQP 1.0 (AMQPNetLite). It sends the canonical envelope as the message body with the §7 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. Artemis gives the binding native settlement, scheduled delivery, a delivery counter and a dead-letter address, so it maps onto them rather than re-implementing.

Install

dotnet add package BabelQueue.Artemis

Requirements: .NET 8. It pulls BabelQueue.Core and AMQPNetLite.Core transitively.

Produce

using Amqp;
using BabelQueue.Artemis;

var connection = new Connection(new Address("amqp://user:pass@localhost:5672"));
var session = new Session(connection);
var sender = new AmqpSenderLink(new SenderLink(session, "orders-sender", "orders"), "orders");

var id = await new ArtemisPublisher(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) for native AMQP scheduled delivery.

Consume

using Amqp;
using BabelQueue;
using BabelQueue.Artemis;

var receiver = new AmqpReceiverLink(new ReceiverLink(session, "orders-worker", "orders"));
var dlq = new AmqpSenderLink(new SenderLink(session, "dlq-sender", "orders.dlq"), "orders.dlq");

var handlers = new Dictionary<string, BabelHandler>
{
    ["urn:babel:orders:created"] = (envelope, message, ct) =>
    {
        // envelope.Data, envelope.TraceId, envelope.Attempts ...
        return Task.CompletedTask;
    },
};

var options = new ArtemisConsumerOptions { DeadLetterSender = dlq, MaxTries = 3 };
var worker = new ArtemisConsumer(receiver, handlers, options);
await worker.RunAsync(cancellationToken); // receive → process → Accept

A successful handler Accepts the message. A throwing handler Releases it (the broker redelivers and bumps delivery-count); once MaxTries is reached the envelope goes to <queue>.dlq with a dead_letter block. The consumer routes on the x-opt-jms-type annotation (falling back to the body URN). Unknown-URN strategy is one of fail / delete / release / dead_letter.

AmqpSenderLink / AmqpReceiverLink wrap the AMQPNetLite links; the publisher and consumer depend on the IAmqpSender / IAmqpReceiver seams, so they unit-test against mocks with no broker.

Contract mapping (§7)

Envelope Apache ActiveMQ Artemis (AMQP 1.0)
body message body (byte-identical across SDKs)
job (URN) x-opt-jms-type annotation → JMSType (consumer routes on this)
trace_id correlation-id → JMSCorrelationID
meta.created_at creation-time → JMSTimestamp (Unix ms)
meta.schema_version application property bq_schema_version
meta.lang application property bq_source_lang
attempts max(body, delivery-count) (AMQP counter is 0-based)
reserve / ack Receive → process → Accept
retry / delay Release redelivery · native x-opt-delivery-time
dead-letter <queue>.dlq + dead_letter block (alongside the native DLA)

The AMQP send/receive seams are mocked with Moq — no Artemis, no network. The envelope is unchanged (schema_version stays 1); Apache ActiveMQ Artemis is purely additive.