Home

/

The Production-Ready Playbook

/

The relay: outbox to queue

The relay: outbox to queue

Chapter 10
Part III
3
min read

The relay: outbox to queue

A row in an outbox table is not yet on a queue. A small relay polls the table, publishes each pending OrderPlaced message to the broker, and marks it sent. This is where Queue-Based Load Levelling (Azure Cloud Design Patterns) begins: the API accepts orders at whatever rate the lunch rush throws at it, and the queue absorbs the spike so the workers downstream see a steady stream instead of a flood.

public async Task Relay(CancellationToken ct)
{
    foreach (var msg in await _outbox.PendingBatch(ct))
    {
        await _publisher.Publish(msg.Topic, msg.Body, ct);   // GCP Pub/Sub (SQS/SNS · Service Bus)
        await _outbox.MarkSent(msg.Id, ct);
    }
}

OrderPlaced is published once and fanned out to several subscribers: the kitchen, courier assignment, the customer's notifications, and analytics. That is Publish/Subscribe (Azure Cloud Design Patterns) doing the fan-out the marketplace needs. The broker is named cloud-agnostically: GCP Pub/Sub first, with AWS SQS/SNS and Azure Service Bus as the parenthetical equivalents. The relay code does not care which; it talks to an IMessagePublisher. Swapping the broker is an adapter change, not a rewrite.

The kitchen and courier workers: competing consumers, strategy

The workers are separate processes. Run one courier-assignment worker, run twenty; they pull from the same subscription and split the work between them without coordinating. That is Competing Consumers (Azure Cloud Design Patterns), and it is the reason this design scales out instead of up. Dinner rush hits, add workers.

The kitchen worker confirms the order with the restaurant and advances its state. The courier-assignment worker decides who delivers it, and that decision is a Strategy (GoF). A CourierMatchingStrategy picks the courier; nearest, fastest, or cheapest are interchangeable implementations chosen per restaurant or per city. Adding a new matching rule is a new strategy and a registration, not a change to the assignment loop.

public interface ICourierMatchingStrategy
{
    Task<CourierId?> Match(Order order, CancellationToken ct);  // nearest / fastest / cheapest
}

public sealed class CourierAssignment(ICourierMatchingStrategy strategy)
{
    public async Task<CourierId?> Assign(Order order, CancellationToken ct) =>
        await strategy.Match(order, ct);   // swap the rule without touching this caller
}

The lifecycle of an order is a State machine (GoF). It moves Placed → Confirmed → Preparing → Ready → PickedUp → Delivered, with Cancelled reachable from the early states, and the transitions are explicit rather than a pile of boolean flags. Each transition is a fact worth recording, which is why this pattern ties so cleanly to the event log behind the live-board view. The kitchen worker drives the cooking transitions; the courier worker drives pickup and delivery.

public async Task Handle(OrderPlaced evt, CancellationToken ct)
{
    var order = await _gateway.Load(evt.OrderId, ct);
    await _gateway.Save(order.Confirm(), ct);     // State: Placed → Confirmed
    await _gateway.Save(order.StartPreparing(), ct); // State: Confirmed → Preparing
}

When a message fails past its retries, it goes to a dead-letter queue rather than vanishing or blocking the line behind it. Backpressure and the dead-letter queue (Enterprise Integration Patterns) are what stop one malformed order from stalling every kitchen worker. The failure is visible, parked, and replayable, which matters more than it sounds at the peak of a Friday-night rush.

the-pareto-stack-cloud-design-patterns-for-small-teams
the-ladder-of-altitudes
how-to-read-this
object-level-the-patterns-that-earn-their-keep
decorator
state
component-level-structuring-one-service
ports-and-adapters-hexagonal
mediator-the-commandquery-split
data-persistence
optimistic-concurrency
messaging-scale
outbox
resilience-staying-up-when-dependencies-dont
rate-limiting-throttling
timeout-fallback
the-composed-pipeline
observability-diagnostics-seeing-inside-production
metrics-the-four-golden-signals
externalised-configuration
hosting-cloud-agnostic-by-default
sidecar-ambassador
orchestrator-agnostic-deploy
a-reference-service
the-relay-outbox-to-queue
the-payment-saga-charge-pay-out-compensate
the-over-engineering-tax
conclusion-production-ready-deliberately
the-pattern-quick-reference-card
altitude-3-data-persistence
altitude-5-resilience
the-skip-list
full-event-sourcing-for-crud
robert-c-martin-uncle-bob-the-house-authority-for-structure
altitude-2-component
altitude-4-messaging-scale
altitude-6-observability-diagnostics

Download the full PDF for free?

Free download — no account required

Get the PDF
Get the PDF
Related Chapters
Free Download
Get the full PDF
All pages, including all code examples, diagrams, and the appendix reference card.
No spam. Unsubscribe at any time.
Your email won't be shared.
Oops! There's a problem with your request. We're working on fixing it. Please try again later.