Customer Story How Bitso cut change failure rate 83% while scaling delivery with coding agents

Testing Microservices with RabbitMQ using Signadot Sandboxes

Testing async microservices is tough when consumers compete for the same queue. This tutorial walks through using Signadot Sandboxes with RabbitMQ to isolate traffic at the request level—no extra brokers needed—so baseline and sandboxed consumers can safely run in parallel on a single exchange.

Introduction

Asynchronous microservices are hard to test: if two versions of a consumer read from the same queue, they compete for messages. Spinning up a separate broker per branch is slow and pricey.

Signadot Sandboxes solve this with request-level isolation. Keep a single RabbitMQ, but route messages only to the intended version (sandbox) of your consumer using a sandbox routing key. Each sandboxed consumer has its own queue binding; baseline traffic remains untouched while you test safely in parallel.

What you will accomplish:

  • Set up a RabbitMQ-based microservices application
  • Use routing keys + selective consumption to isolate sandbox traffic
  • Deploy services to Kubernetes
  • Create Signadot sandboxes for isolated testing
  • Test message routing between baseline and sandbox environments

Time required: 45-60 minutes

Prerequisites

Before starting, ensure you have:

  1. Signadot CLI installed - Follow the installation guide
  2. Docker Desktop running locally
  3. kubectl configured to access your Kubernetes cluster
  4. Python 3.8+ and pip
  5. RabbitMQ knowledge - Basic understanding of exchanges, queues, and routing

Architecture Overview

  • A topic exchange fans out each message to both baseline and sandbox queues.
  • OTel propagates the sandbox routing key automatically as W3C baggage.
  • Selective consumption in consumers:
    • Baseline consumer processes messages with no routing key, and skips messages whose key belongs to an active sandbox of this same service. It still processes messages carrying keys for other services’ sandboxes or inactive/unknown keys.
    • Sandbox consumer processes only messages with its own sandbox key.

This gives isolation without multiple brokers: everyone receives the message, but only the right consumer acts on it.

The idea in one picture

Baseline and sandbox consumers each have their own queue bound to the same exchange. Routing keys in headers determine who should act.

Flow diagram of a RabbitMQ topic exchange fanning messages out to baseline and sandbox queues, with each consumer processing or skipping messages based on the sandbox routing key

Project structure

You can scaffold this from your own repo or adapt from Signadot examples.

Step 1 – Get the project

Clone the example repository from GitHub:

Inside the repo you’ll find:

  • publisher/ — Flask app with /publish and /events. Publishes to a topic exchange and logs events to Redis.
  • consumer/ — Python consumer that declares its own queue, binds to the exchange, and processes messages.
  • k8s/ — manifests for RabbitMQ, Redis, publisher, consumer, and a namespace.
  • signadot/ — sandbox specs for publisher/consumer and a RouteGroup to bind them under a single routing key.

Step 2 — Build and push images

If you’ll test a dev version in a sandbox, also push a :dev tag.

Step 3 — Deploy the baseline stack

Create namespace and infra:

Deploy services:

Verify pods:

Step 4 — Connect to Signadot Locally

Use Signadot Local Connect to reach cluster services directly from your local machine:

Send a baseline message (no sandbox key):

Check:

You should see baseline processing.

Terminal showing curl publishing a baseline order to the publisher service and kubectl logs confirming the baseline consumer processed it

Step 5 — Create sandboxes and a RouteGroup

Spin up forked workloads for publisher-v2 and consumer-v2:

Create a RouteGroup that ties them together under one routing key / URL (optional):

Step 6 — Prove isolation

Baseline message (no header → baseline handles it):

curl publishing order 002 without a sandbox header, with the response showing routing_key baseline and status published

Sandbox message (explicit header → sandbox handles it):

Terminal fetching the sandbox routing key with signadot sandbox get and publishing order 003 with the sd-routing-key baggage header

Watch logs side-by-side:

  • Baseline:

kubectl logs showing the baseline consumer processing only baseline orders 001 and 002

  • Sandbox (replace name with your sandbox consumer deployment)

kubectl logs showing the sandboxed consumer-v2 processing only order 003 tagged with the sandbox routing key

Expected outcome:

  • Baseline processes only messages without a sandbox key or routing key of sandboxes of other services
  • Sandbox processes only messages with its own key

What’s Happening Under the Hood

  • Context propagation: When you hit a sandbox URL or pass sd-routing-key, the publisher attaches that key to message headers.
  • Fan-out delivery: RabbitMQ’s topic exchange routes messages to both baseline and sandbox queues (via their bindings).
  • Selective consumption:
    • Baseline consumer accepts messages without a sandbox key; it skips messages with a key that belongs to an active sandbox.
    • Sandbox consumer processes only messages whose sd-routing-key equals its sandbox ID.
  • No message loss: Each sandbox has its own queue bound to the exchange. Messages always have a target consumer; baseline only “avoids” messages while a matching sandbox is active.

Conclusion

You’ve built and deployed a minimal RabbitMQ publisher/consumer stack, forked services into Signadot sandboxes, and confirmed that sandbox routing keys isolate messages in a shared RabbitMQ. This approach scales beyond this demo:

  • Works with Kafka, Pub/Sub, or SQS (using analogous header/attribute + selective consumption patterns)
  • Supports multiple sandboxes concurrently (e.g., per PR)
  • Integrates naturally with CI/CD to spin up ephemeral test envs for each change

For deeper dives, see:

Happy testing!

Stay in the loop

Get the latest updates from Signadot

Validate code as fast as agents write it.