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.
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)
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.
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.
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.
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.