// ───────────────────────────────────────────────────────────────────────────── // FACADE [Structural] // ───────────────────────────────────────────────────────────────────────────── // INTENT : Provide a unified, higher-level interface to a set of interfaces in a subsystem, making the // subsystem easier to use. // PROBLEM : Placing an order means talking to inventory, payments, shipping and email in the right order // with the right rollback — and three controllers each re-implement that dance. // SAY : Participants: Facade (one simple entry point), Subsystem classes (do the real work, unaware of // the facade), Client. Pitch: "one door in front of a messy building." // SEEN IN : `fs.readFile` (open/fstat/read/close behind one call); `fetch` / axios over sockets, redirects, // body parsing; `express()` over `http.createServer` + router + parsers; jQuery `$.ajax`, `$(sel)`; // `child_process.execSync` over `spawn`; service-layer functions (`placeOrder()`) in Node backends; // SDK clients (`stripe.checkout.sessions.create`) hiding auth, retries, serialization. // VS : Adapter converts an interface so it matches an EXISTING one; Facade invents a NEW simpler // interface over several objects. Mediator coordinates peers that talk BACK to it; Facade is // one-way — subsystems don't know it exists. // ───────────────────────────────────────────────────────────────────────────── 'use strict'; const assert = require('node:assert/strict'); // ── Classic shape (classes, so the participant names are visible) // Subsystems — each fine alone, none knows about the others class InventoryService { constructor() { this.stock = new Map([['SKU-1', 5], ['SKU-2', 0]]); } available(sku, qty) { return (this.stock.get(sku) ?? 0) >= qty; } reserve(sku, qty) { this.stock.set(sku, this.stock.get(sku) - qty); } release(sku, qty) { this.stock.set(sku, this.stock.get(sku) + qty); } } class PaymentService { constructor() { this.charges = []; } charge(customerId, cents) { if (customerId === 'cust-declined') throw new Error('card declined'); const id = `ch_${this.charges.length + 1}`; this.charges.push({ id, customerId, cents }); return id; } } class ShippingService { constructor() { this.labels = []; } createLabel(address, items) { const id = `lbl_${this.labels.length + 1}`; this.labels.push({ id, address, items }); return id; } } class EmailService { constructor() { this.sent = []; } send(to, subject) { this.sent.push({ to, subject }); } } class OrderFacade { // Facade — the one call controllers/CLI/cron need constructor({ inventory, payment, shipping, email }) { Object.assign(this, { inventory, payment, shipping, email }); } placeOrder({ customerId, email, address, sku, qty, unitCents }) { if (!this.inventory.available(sku, qty)) return { ok: false, reason: 'out_of_stock' }; this.inventory.reserve(sku, qty); let chargeId; try { chargeId = this.payment.charge(customerId, qty * unitCents); } catch (err) { this.inventory.release(sku, qty); // compensating action lives in ONE place return { ok: false, reason: err.message }; } const labelId = this.shipping.createLabel(address, [{ sku, qty }]); this.email.send(email, `Order confirmed (${chargeId})`); return { ok: true, chargeId, labelId }; } } const sub = { inventory: new InventoryService(), payment: new PaymentService(), shipping: new ShippingService(), email: new EmailService() }; const orders = new OrderFacade(sub); const base = { customerId: 'cust-1', email: 'a@x.io', address: '1 Main', sku: 'SKU-1', qty: 2, unitCents: 1500 }; assert.deepEqual(orders.placeOrder(base), { ok: true, chargeId: 'ch_1', labelId: 'lbl_1' }); assert.equal(sub.inventory.stock.get('SKU-1'), 3); assert.equal(sub.payment.charges[0].cents, 3000); assert.deepEqual(sub.email.sent, [{ to: 'a@x.io', subject: 'Order confirmed (ch_1)' }]); assert.deepEqual(orders.placeOrder({ ...base, sku: 'SKU-2', qty: 1 }), { ok: false, reason: 'out_of_stock' }); assert.equal(sub.shipping.labels.length, 1); // nothing downstream happened assert.deepEqual(orders.placeOrder({ ...base, customerId: 'cust-declined', qty: 3 }), { ok: false, reason: 'card declined' }); assert.equal(sub.inventory.stock.get('SKU-1'), 3); // reservation rolled back by the facade assert.equal(sub.email.sent.length, 1); assert.equal(sub.payment.charge('cust-2', 1), 'ch_2'); // subsystems stay directly usable; facade is a shortcut // ── Idiomatic JS shape: a module exporting one function over several imports/built-ins const { createHash } = require('node:crypto'); const sha256 = (s) => createHash('sha256').update(s).digest('hex'); // facade over the streaming Hash API assert.equal(sha256('abc'), 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'); const parseEnv = (text) => Object.fromEntries( // facade over split/trim/filter/indexOf/replace text.split('\n').map((l) => l.trim()).filter((l) => l && !l.startsWith('#')) .map((l) => { const i = l.indexOf('='); return [l.slice(0, i), l.slice(i + 1).replace(/^"|"$/g, '')]; }), ); assert.deepEqual(parseEnv('# db\nDB_URL="pg://x"\n\nPORT=3000\n'), { DB_URL: 'pg://x', PORT: '3000' }); const { URL } = require('node:url'); const isSameOrigin = (a, b) => new URL(a).origin === new URL(b).origin; // facade over URL parsing rules assert.equal(isSameOrigin('https://x.io/a', 'https://x.io:443/b'), true); assert.equal(isSameOrigin('https://x.io', 'http://x.io'), false); console.log('ok: design-patterns/structural/facade.js');