Redis transport

BabelQueue.Redis is a Redis transport on the .NET core, built on StackExchange.Redis. It sends the canonical envelope as a Redis list element with the §1 reliable-queue pattern, 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.

Unlike the SQS or RabbitMQ bindings, a Redis list element carries no native metadata, so there is no header/attribute projection: the list element is the canonical envelope JSON, byte-for-byte, with no wrapping and no added fields. A consumer in any language pops that same body and decodes it.

Install

dotnet add package BabelQueue.Redis

Requirements: .NET 8. It pulls BabelQueue.Core and StackExchange.Redis transitively.

Produce

using BabelQueue.Redis;
using StackExchange.Redis;

var redis = await ConnectionMultiplexer.ConnectAsync("localhost:6379");
IDatabase db = redis.GetDatabase();

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

PublishAsync(urn, data) returns the message meta.id; pass a traceId to continue a trace. Produce is RPUSH <queue> <envelope>.

Consume

using BabelQueue;
using BabelQueue.Redis;

var handlers = new Dictionary<string, BabelHandler>
{
    ["urn:babel:orders:created"] = async (env, rawBody, ct) =>
    {
        // env.Data, env.TraceId, env.Attempts ...
    },
};

var consumer = new RedisConsumer(db, "orders", handlers, new RedisConsumerOptions
{
    OnError = (err, env, raw) => Console.Error.WriteLine(err),
});

await consumer.RunAsync(cancellationToken); // polls until cancelled

A reserve atomically moves the head of the queue onto a per-queue <queue>:processing list (LMOVE <queue> <queue>:processing LEFT RIGHT), so an in-flight message survives a crash; a successful handler LREMs it. Retry is at-least-once: a throwing handler leaves the element on the processing list. The poll loop never stops on a bad message — observe via OnError / OnUnknownUrn. Unknown-URN strategy is one of fail / delete / release / dead_letter.

Contract mapping (§1)

Envelope Redis
body the list element (byte-identical across SDKs, no wrapping)
job (URN) read from the decoded body (no native metadata to route on)
trace_id / meta.id / … read from the decoded body
produce RPUSH <queue> <envelope>
reserve LMOVE <queue> <queue>:processing LEFT RIGHT (an in-flight message survives a crash)
ack LREM <queue>:processing 1 <envelope>

This is a .NET-owned reliable queue, mirroring the Go reference: produce/reserve/ack are self-consistent and crash-safe on a queue this SDK owns end-to-end. Full parity with Laravel’s reserved-sorted-set reservation on a shared PHP+.NET Redis queue is a separate task — see broker-bindings §1.4. IDatabase is an interface, so the publisher and consumer unit-test against a mock with no Redis and no network. The envelope is unchanged (schema_version stays 1); Redis support is purely additive.