Symptom

Several subsystems must be orchestrated together to accomplish a single user-visible task, and every consumer that wants the task has to reimplement the orchestration: call A, check result, call B, on B's failure undo A, call C, then D, then E. The orchestration is a recipe everyone reinvents, often subtly wrong.

Goal

One named entry point that owns the choreography. Consumers call submitOrder(cart, customer) and never see the five subsystems behind it. The recipe lives in one place; mistakes (forgetting to release inventory on payment failure) are fixed once.

Before the pattern

function checkout(cart, customer) {
if (!inventory.reserve(cart.items)) {
throw new Error('out of stock');
}
const charge = payment.charge(cart.total, customer.cardToken);
if (!charge.success) {
inventory.release(cart.items);
throw new Error('payment failed');
}
const shipment = shipping.createShipment(cart.items, customer.address);
notifications.sendOrderConfirmation(customer.email, shipment.trackingNumber);
audit.recordPurchase(customer.id, cart.total);
return { orderId: shipment.trackingNumber, total: cart.total };
}
// Every consumer that calls checkout knows about 5 subsystems
// and the right ordering. Forgetting inventory.release on payment-fail
// silently leaks reserved inventory.

After the pattern

class CheckoutFacade {
constructor(inventory, payment, shipping, notifications, audit) {
this.inventory = inventory;
this.payment = payment;
this.shipping = shipping;
this.notifications = notifications;
this.audit = audit;
}
submitOrder(cart, customer) {
if (!this.inventory.reserve(cart.items)) {
throw new Error('out of stock');
}
const charge = this.payment.charge(cart.total, customer.cardToken);
if (!charge.success) {
this.inventory.release(cart.items);
throw new Error('payment failed');
}
const shipment = this.shipping.createShipment(cart.items, customer.address);
this.notifications.sendOrderConfirmation(customer.email, shipment.trackingNumber);
this.audit.recordPurchase(customer.id, cart.total);
return { orderId: shipment.trackingNumber, total: cart.total };
}
}
// Client: checkout.submitOrder(cart, customer)
Example source: Illustrative example written for this site in the spirit of Design Patterns (Gamma, Helm, Johnson, Vlissides, Addison-Wesley, 1994), chapter 4. The book's running example is a compiler with parser/lexer/codegen subsystems; this JavaScript adaptation uses an e-commerce checkout because the multi-subsystem choreography (inventory + payment + shipping + notifications + audit) makes the encapsulation payoff more visible.
Pressure

Per-consumer orchestration is the worst form of Insider Trading — every consumer knows about every subsystem's interface, error modes, and ordering constraints. Adding a new subsystem (e.g., fraud check) requires hunting every consumer; changing one subsystem's interface ripples to every consumer in lockstep.

Tradeoff

Facade hides the subsystems but also hides which subsystems exist. Debugging a runtime error inside CheckoutFacade.submitOrder requires reading through five subsystem calls; consumers can't easily intervene mid-flow (e.g., apply a discount between inventory.reserve and payment.charge). The Facade is opaque by design, and opacity has a cost.

Relief

One method per user task; the orchestration lives in one tested file. New subsystems land as facade-internal additions; consumers stay unchanged. The recipe stops being tribal knowledge.

Trap

Facades that grow into general-purpose services (CheckoutFacade.submitOrder, CheckoutFacade.refund, CheckoutFacade.modify, CheckoutFacade.audit) lose the single-task focus and become god objects. Stop and split when a facade has more than one or two user tasks; if every team adds 'one more method' it becomes the Service that everyone fears.