Put one coordinator in charge of a multi-step workflow, so the sequence and failure handling live in a single, readable place.
Placing an order might mean: reserve inventory, charge payment, schedule shipping. If each service calls the next one directly (choreography), the overall sequence isn't written down anywhere — you have to read every service to reconstruct the flow, and if step two fails, nothing coordinates undoing step one.
An orchestrator calls each step in order, waits for the result, and decides what happens next — including running compensating actions in reverse if a later step fails. The workflow's logic lives in one place instead of being implied by which service happens to call which.
Each step exposes a matching `undo`. If a step throws, the orchestrator walks backwards through the steps it already completed and compensates them in reverse order.
const inventory = {
reserve: async (id) => console.log(`inventory: reserved ${id}`),
release: async (id) => console.log(`inventory: released ${id}`),
};
const payments = {
charge: async (id) => {
console.log(`payments: charging order ${id}`);
if (id === "bad-order") throw new Error("card declined");
},
refund: async (id) => console.log(`payments: refunded order ${id}`),
};
const shipping = {
schedule: async (id) => console.log(`shipping: scheduled for ${id}`),
cancel: async (id) => console.log(`shipping: cancelled for ${id}`),
};
async function placeOrder(orderId) {
const completed = [];
const steps = [
{ name: "inventory", run: () => inventory.reserve(orderId), undo: () => inventory.release(orderId) },
{ name: "payments", run: () => payments.charge(orderId), undo: () => payments.refund(orderId) },
{ name: "shipping", run: () => shipping.schedule(orderId), undo: () => shipping.cancel(orderId) },
];
try {
for (const step of steps) {
await step.run();
completed.push(step);
}
console.log(`order ${orderId}: completed`);
} catch (err) {
console.log(`order ${orderId}: failed at a step (${err.message}), compensating...`);
for (const step of completed.reverse()) {
await step.undo();
}
}
}
await placeOrder("order-123");
await placeOrder("bad-order");