Orchestration vs Choreography
| Pattern | Description | Pros | Cons |
|---|---|---|---|
| Orchestration | Central coordinator directs the flow | Easy to understand, centralized logic | Single point of coordination |
| Choreography | Services react to events autonomously | Decoupled services | Flow harder to trace |
QueueBear workflows excel at orchestration - using a central workflow to coordinate multiple services.
Basic Orchestration Pattern
import { serve } from "queuebear";
export const POST = serve<CheckoutInput>(async (ctx) => {
const { orderId, customerId, items, paymentMethod, shippingAddress } = ctx.input;
// 1. Validate order with Inventory Service
const inventory = await ctx.run("validate-inventory", async () => {
const response = await fetch("https://inventory.internal/validate", {
method: "POST",
body: JSON.stringify({ items }),
});
if (!response.ok) throw new Error("Inventory validation failed");
return response.json();
});
// 2. Calculate pricing with Pricing Service
const pricing = await ctx.run("calculate-pricing", async () => {
const response = await fetch("https://pricing.internal/calculate", {
method: "POST",
body: JSON.stringify({ items, customerId }),
});
return response.json();
});
// 3. Process payment with Payment Service
const payment = await ctx.run("process-payment", async () => {
const response = await fetch("https://payments.internal/charge", {
method: "POST",
body: JSON.stringify({
customerId,
amount: pricing.total,
paymentMethod,
orderId,
}),
});
if (!response.ok) throw new Error("Payment failed");
return response.json();
});
// 4. Create shipment with Shipping Service
const shipment = await ctx.run("create-shipment", async () => {
const response = await fetch("https://shipping.internal/create", {
method: "POST",
body: JSON.stringify({ orderId, items, address: shippingAddress }),
});
return response.json();
});
// 5. Send confirmation with Notification Service
await ctx.run("send-confirmation", async () => {
await fetch("https://notifications.internal/send", {
method: "POST",
body: JSON.stringify({
type: "order_confirmed",
customerId,
data: { orderId, shipmentId: shipment.id },
}),
});
});
return {
success: true,
orderId,
paymentId: payment.id,
shipmentId: shipment.id,
};
});
Parallel Service Calls
When services are independent, call them in parallel for better performance:
export const POST = serve<DashboardInput>(async (ctx) => {
const { userId } = ctx.input;
// Fetch from multiple services in parallel
const [user, orders, recommendations, notifications] = await ctx.parallel([
{
name: "fetch-user",
fn: async () => {
const res = await fetch(`https://users.internal/users/${userId}`);
return res.json();
},
},
{
name: "fetch-orders",
fn: async () => {
const res = await fetch(`https://orders.internal/users/${userId}/orders`);
return res.json();
},
},
{
name: "fetch-recommendations",
fn: async () => {
const res = await fetch(`https://recommendations.internal/users/${userId}`);
return res.json();
},
},
{
name: "fetch-notifications",
fn: async () => {
const res = await fetch(`https://notifications.internal/users/${userId}`);
return res.json();
},
},
]);
return { user, orders, recommendations, notifications };
});
Hybrid: Orchestration with Event Coordination
Combine orchestration with events for complex flows that depend on external triggers:
export const POST = serve<SubscriptionInput>(async (ctx) => {
const { customerId, planId } = ctx.input;
// Step 1: Create subscription
const subscription = await ctx.run("create-subscription", async () => {
return await billingService.createSubscription(customerId, planId);
});
// Step 2: Wait for first payment (event from payment processor webhook)
const paymentEvent = await ctx.waitForEvent(
"wait-first-payment",
"payment.completed",
{
eventKey: subscription.id,
timeoutSeconds: 3600, // 1 hour for payment to process
}
);
// Step 3: Activate subscription
await ctx.run("activate-subscription", async () => {
await billingService.activate(subscription.id);
});
// Step 4: Provision resources
await ctx.run("provision-resources", async () => {
await provisioningService.setup(customerId, planId);
});
return { subscriptionId: subscription.id, status: "active" };
});
// External: Payment processor webhook sends event
app.post("/webhooks/payment-processor", async (req, res) => {
const { subscriptionId, status } = req.body;
if (status === "succeeded") {
await qb.workflows.sendEvent("payment.completed", {
eventKey: subscriptionId,
payload: { amount: req.body.amount },
});
}
res.json({ received: true });
});
Example: E-Commerce with 5 Services
A complete checkout spanning Orders, Inventory, Payments, Shipping, and Notifications:
export const POST = serve<CheckoutInput>(async (ctx) => {
const { orderId, customerId, items, payment, shipping } = ctx.input;
// === PHASE 1: Validation (parallel) ===
const [inventoryStatus, customerStatus] = await ctx.parallel([
{
name: "check-inventory",
fn: async () => {
const res = await fetch("https://inventory.internal/check", {
method: "POST",
body: JSON.stringify({ items }),
});
const data = await res.json();
if (!data.available) throw new Error(`Out of stock: ${data.unavailable}`);
return data;
},
},
{
name: "validate-customer",
fn: async () => {
const res = await fetch(`https://customers.internal/validate/${customerId}`);
const data = await res.json();
if (data.blocked) throw new Error("Customer account blocked");
return data;
},
},
]);
// === PHASE 2: Create Order ===
const order = await ctx.run("create-order", async () => {
const res = await fetch("https://orders.internal/create", {
method: "POST",
body: JSON.stringify({ orderId, customerId, items, status: "pending" }),
});
return res.json();
});
// === PHASE 3: Payment Processing ===
const paymentResult = await ctx.run("process-payment", async () => {
const res = await fetch("https://payments.internal/charge", {
method: "POST",
body: JSON.stringify({ customerId, orderId, amount: order.total }),
});
if (!res.ok) throw new Error("Payment failed");
return res.json();
});
// === PHASE 4: Fulfillment (parallel) ===
try {
const [inventoryReservation, shipmentCreation] = await ctx.parallel([
{
name: "reserve-inventory",
fn: async () => {
const res = await fetch("https://inventory.internal/reserve", {
method: "POST",
body: JSON.stringify({ orderId, items }),
});
return res.json();
},
},
{
name: "create-shipment",
fn: async () => {
const res = await fetch("https://shipping.internal/create", {
method: "POST",
body: JSON.stringify({ orderId, items, address: shipping.address }),
});
return res.json();
},
},
]);
// === PHASE 5: Send Notifications (parallel) ===
await ctx.parallel([
{
name: "send-confirmation-email",
fn: () => fetch("https://notifications.internal/email", {
method: "POST",
body: JSON.stringify({ type: "order_confirmation", customerId, orderId }),
}),
},
{
name: "send-sms",
fn: () => customerStatus.phone && fetch("https://notifications.internal/sms", {
method: "POST",
body: JSON.stringify({ phone: customerStatus.phone, orderId }),
}),
},
]);
return {
success: true,
orderId,
paymentId: paymentResult.id,
shipmentId: shipmentCreation.id,
};
} catch (fulfillmentError) {
// === COMPENSATION: Refund on failure ===
await ctx.run("refund-payment", async () => {
await fetch("https://payments.internal/refund", {
method: "POST",
body: JSON.stringify({ paymentId: paymentResult.id }),
});
});
throw fulfillmentError;
}
});
Best Practices
1. Propagate Correlation IDs
export const POST = serve<Input>(async (ctx) => {
const correlationId = ctx.runId; // Use workflow run ID
await ctx.run("call-service", async () => {
await fetch(url, {
headers: {
"X-Correlation-ID": correlationId,
"X-Workflow-Run-ID": ctx.runId,
},
});
});
});
2. Use Timeouts
await ctx.run("call-external", async () => {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
try {
return await fetch(url, { signal: controller.signal });
} finally {
clearTimeout(timeout);
}
});
3. Design for Idempotency
// Pass orderId as idempotency key to each service
await paymentService.charge({
orderId, // Service uses this to prevent duplicate charges
amount,
});
Summary
| Pattern | Use When |
|---|---|
| Sequential orchestration | Operations must happen in order |
| Parallel orchestration | Independent operations |
| Event-based hybrid | Complex flows with external triggers |
| Compensation | Rollback on partial failures |