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.