Broker setup recipes

A copy-paste recipe per SDK for the two most common brokers — Redis and RabbitMQ — so a message produced in one language is consumed in another over the broker you already run. Every recipe emits and reads the exact canonical envelope (schema_version: 1); only the wiring differs. The body is identical everywhere, so any pairing interoperates — see the broker bindings for how each broker carries it natively.

Seven brokers are GA — these recipes cover two of them. Redis and RabbitMQ are the worked examples below; the same shape extends to Amazon SQS, Azure Service Bus, Apache Pulsar, Apache Kafka and Apache ActiveMQ/Artemis, all GA and conformance-locked. For those five, swap in the matching transport/extra — Go …/sqs · …/pulsar · …/kafka · …/artemis · …/azureservicebus, Python [sqs] [pulsar] [kafka] [artemis] (+ Azure SB), Node @babelqueue/sqs · …/pulsar · …/kafka · …/artemis · …/azure-service-bus, Java babelqueue-sqs/-pulsar/-kafka/-artemis/-azureservicebus, .NET BabelQueue.Sqs/.Pulsar/.Kafka/.Artemis/.AzureServiceBus, and PHP php-sdk SqsTransport / KafkaTransport+KafkaConsumer / PulsarTransport+PulsarConsumer / StompTransport (Artemis) plus the Laravel babelqueue-sqs and babelqueue-artemis drivers. The one documented gap is PHP × Azure Service Bus. The per-broker projection is in the broker bindings; the keys are in the configuration reference.

Runtime vs core-only. The shapes differ by language: a runtime SDK (Laravel, Symfony, Python, Go’s App, Node adapters) gives you publish/worker out of the box, while a core-only SDK (.NET, Java core) is a codec — you move the bytes with your own broker client, or a dedicated transport where one ships (e.g. BabelQueue.Redis, com.babelqueue:babelqueue-redis). Where a language has no first-party RabbitMQ transport on the core, the recipe uses its framework adapter (MassTransit, Spring AMQP), which is the supported path.

All recipes use the same queue (orders) and URN (urn:babel:orders:created) so you can mix any producer with any consumer.

PHP / Laravel

Laravel plugs in as a custom queue connection. Pick the driver per broker in config/queue.php:

// config/queue.php — Redis
'connections' => [
    'babelqueue' => [
        'driver'      => 'babelqueue-redis',
        'connection'  => 'default',
        'queue'       => env('BABELQUEUE_QUEUE', 'orders'),
        'retry_after' => 90,
    ],
],
// config/queue.php — RabbitMQ
'connections' => [
    'babelqueue' => [
        'driver' => 'babelqueue-rabbitmq',
        'host'   => env('RABBITMQ_HOST', '127.0.0.1'),
        'port'   => env('RABBITMQ_PORT', 5672),
        'queue'  => env('BABELQUEUE_QUEUE', 'orders'),
    ],
],

Publish with the facade and run the standard worker against the connection:

use BabelQueue\Facades\BabelQueue;

BabelQueue::publish('urn:babel:orders:created', ['order_id' => 1042]);
php artisan queue:work babelqueue

URN→handler mapping and the dead-letter policy live in config/babelqueue.php — see Laravel configuration.

PHP / Symfony

Symfony Messenger keeps its own transport DSN — redis:// or amqp:// — and you swap in the BabelQueue serializer so the wire format is the canonical envelope:

# config/packages/messenger.yaml
framework:
    messenger:
        transports:
            babel:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'   # redis://localhost:6379/messages
                                                        # or amqp://guest:guest@localhost:5672/%2f/messages
                serializer: 'babelqueue.messenger.serializer'
        routing:
            'App\Message\OrderCreated': babel

Map inbound URNs back to message classes in config/packages/babelqueue.yaml, then dispatch and consume the Messenger way (messenger:consume babel). See Symfony configuration.

PHP (framework-less core)

The babelqueue/php-sdk core is a codec; its optional reference transports move the bytes. RedisTransport (over predis/predis) and AmqpTransport (over php-amqplib) both publish the encoded envelope:

use BabelQueue\Codec\EnvelopeCodec;
use BabelQueue\Transport\RedisTransport;

$transport = new RedisTransport(new Predis\Client('redis://localhost:6379'));
$env = EnvelopeCodec::make('urn:babel:orders:created', ['order_id' => 1042], 'orders');
$transport->publish(EnvelopeCodec::encode($env), 'orders');

Swap RedisTransport for AmqpTransport to publish to RabbitMQ — see PHP transports.

Python

The Python runtime chooses its transport from the broker URL scheme — redis:// or amqp://. Install the matching extra:

# pip install "babelqueue[redis]"   →  Redis
# pip install "babelqueue[amqp]"     →  RabbitMQ (via pika)
from babelqueue import BabelQueue

app = BabelQueue("redis://localhost:6379/0", queue="orders")
# app = BabelQueue("amqp://guest:guest@localhost:5672/", queue="orders")

app.publish("urn:babel:orders:created", {"order_id": 1042})

Register handlers and run the worker as in Python configuration.

Go

The Go core is a codec; the optional zero-dependency App runtime plus a per-broker transport module gives you publish/consume. Install the module you need:

// go get github.com/babelqueue/babelqueue-go/redis   →  Redis
// go get github.com/babelqueue/babelqueue-go/amqp     →  RabbitMQ
import (
	babelqueue "github.com/babelqueue/babelqueue-go"
	"github.com/babelqueue/babelqueue-go/redis"
)

tr, err := redis.New("redis://localhost:6379/0") // amqp.New("amqp://guest:guest@localhost:5672/")
if err != nil {
	return err
}
defer tr.Close()

app := babelqueue.NewApp(tr, babelqueue.WithDefaultQueue("orders"))
app.Handle("urn:babel:orders:created", func(ctx context.Context, env babelqueue.Envelope) error {
	return nil // env.Data, env.TraceID
})

app.Publish(ctx, "urn:babel:orders:created", map[string]any{"order_id": 1042})
return app.Consume(ctx) // blocks

The Redis transport runs the reliable-queue pattern; AMQP uses durable queues with manual ack. See Runtime & transports.

Node.js

Beyond @babelqueue/core, thin transport packages wire it to each broker — you bring the client:

// npm install @babelqueue/redis ioredis        →  Redis
import Redis from "ioredis";
import { RedisPublisher, RedisConsumer } from "@babelqueue/redis";

const client = new Redis("redis://localhost:6379/0");

await RedisPublisher.create(client, "orders")
  .publish("urn:babel:orders:created", { order_id: 1042 });

const consumer = new RedisConsumer(client, "orders", {
  "urn:babel:orders:created": (env) => console.log(env.data.order_id, env.trace_id),
}, { maxTries: 3 });
await consumer.run(() => true);
// npm install @babelqueue/rabbitmq amqplib      →  RabbitMQ
import amqp from "amqplib";
import { RabbitMQPublisher } from "@babelqueue/rabbitmq";

const channel = await (await amqp.connect("amqp://guest:guest@localhost:5672/")).createChannel();
await channel.assertQueue("orders", { durable: true });

await RabbitMQPublisher.create(channel, "orders")
  .publish("urn:babel:orders:created", { order_id: 1042 });

For a BullMQ/NestJS stack on Redis, use @babelqueue/bullmq / @babelqueue/nestjs — see Adapters & transports.

Java

The Java core is a codec. For Redis it ships a first-party transport, com.babelqueue:babelqueue-redis (built on Lettuce):

import com.babelqueue.redis.RedisPublisher;
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.sync.RedisCommands;
import java.util.Map;

RedisClient client = RedisClient.create("redis://localhost:6379");
RedisCommands<String, String> redis = client.connect().sync();

RedisPublisher.create(redis, "orders")
    .publish("urn:babel:orders:created", Map.of("order_id", 1042L));

For RabbitMQ, use the Spring Boot adapter com.babelqueue:babelqueue-spring: its auto-configured MessageConverter wires the canonical envelope into RabbitTemplate (producing) and @RabbitListener (consuming):

@Service
class Orders {
    private final BabelQueuePublisher babelQueue;
    Orders(BabelQueuePublisher babelQueue) { this.babelQueue = babelQueue; }

    void create() {
        babelQueue.publish("urn:babel:orders:created", Map.of("order_id", 1042L), "orders");
    }
}

See the Redis transport and Spring Boot adapter.

.NET

The .NET core is a codec. For Redis it ships BabelQueue.Redis (built on StackExchange.Redis):

using BabelQueue.Redis;
using StackExchange.Redis;

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

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

For RabbitMQ, use the MassTransit adapter BabelQueue.MassTransit: register its System.Text.Json envelope converter so MassTransit’s RabbitMQ transport carries the canonical envelope. (You can also encode with EnvelopeCodec.Encode(...) and publish with your own RabbitMQ.Client channel.) See the Redis transport and MassTransit adapter.

Cross-language sanity check

Because every recipe writes the same envelope to the same orders queue, you can mix producers and consumers freely — e.g. a Python redis:// producer and a Go …/redis consumer, or a Laravel babelqueue-rabbitmq producer and a Node @babelqueue/rabbitmq consumer. For a runnable end-to-end demo, see Cross-language: Python → Go over Redis.

Continue to Dead-letter & tracing.