Facade

Placing an order took four calls, and the sequence mattered: reserve the stock, charge the customer, send the ticket to the kitchen, assign a rider. The mobile team got it right. The web team charged first and reserved second. About 40 customers a week paid for a dish that had sold out a few seconds earlier, and support refunded each one by hand.

Both teams had the same four classes and the same documentation. Only one had read it.

Your answer

Placing an order requires calling Inventory, Payments, Kitchen and Riders in a specific order, with stock released if the payment fails. Three client teams need to do this. How do you make it impossible for one of them to get the order wrong?

The idea

The picture to keep
The hotel reception

You dial 9 from your room and say “towels, dinner at eight, and a taxi at six tomorrow”. You did not call housekeeping, the kitchen and the travel desk separately, and you do not know their extension numbers. The desk knows who does what and in which order. The departments still exist. You have been given one number to deal with all of them.

In one line: One simple front door for a complicated set of classes.

Before: every client is an expert

class Inventory {
  reserve(dish: string): string {
    return `reserved ${dish}`
  }
}
 
class Payments {
  charge(amount: number): string {
    return `charged ${amount}`
  }
}
 
class Kitchen {
  ticket(dish: string): string {
    return `kitchen ticket for ${dish}`
  }
}
 
class Riders {
  assign(address: string): string {
    return `rider assigned to ${address}`
  }
}
 
// The web client's version. Spot the bug.
const inventory = new Inventory()
const payments = new Payments()
 
const steps = [payments.charge(120), inventory.reserve('Masala dosa')]
 
console.log(steps.join(', ')) // charged 120, reserved Masala dosa

It ran, it printed, and it is wrong: money was taken before stock was confirmed. Every client has to know four classes and the one correct order to call them in. That knowledge is copied into each client, and a copy can drift.

After: one method that knows

class Inventory {
  reserve(dish: string): string {
    return `reserved ${dish}`
  }
}
 
class Payments {
  charge(amount: number): string {
    return `charged ${amount}`
  }
}
 
class Kitchen {
  ticket(dish: string): string {
    return `kitchen ticket for ${dish}`
  }
}
 
class Riders {
  assign(address: string): string {
    return `rider assigned to ${address}`
  }
}
 
class OrderFacade {
  constructor(
    private readonly inventory: Inventory,
    private readonly payments: Payments,
    private readonly kitchen: Kitchen,
    private readonly riders: Riders,
  ) {}
 
  placeOrder(dish: string, amount: number, address: string): string[] {
    return [
      this.inventory.reserve(dish),
      this.payments.charge(amount),
      this.kitchen.ticket(dish),
      this.riders.assign(address),
    ]
  }
}
 
const orders = new OrderFacade(
  new Inventory(),
  new Payments(),
  new Kitchen(),
  new Riders(),
)
 
for (const step of orders.placeOrder('Masala dosa', 120, 'HSR Layout')) {
  console.log(step)
}
// > reserved Masala dosa
// > charged 120
// > kitchen ticket for Masala dosa
// > rider assigned to HSR Layout

The sequence is written once, inside placeOrder. A client calls one method with three values. It cannot charge before reserving, because it never calls either of them.

When the business adds a fifth step, say a fraud check before the charge, it goes into placeOrder and every client has it the same day.

Figure 1. Clients talk to the facade. The facade talks to the four subsystems, in the right order, so that knowledge lives in exactly one place.

What a facade is, and is not

A facade adds no new ability. Everything placeOrder does, a client could do by hand. It exists to make the common task short and hard to get wrong.

It also does not lock anything away. The four classes are still there, and an admin tool that needs to release stock manually can call Inventory directly. A facade is a convenient door. It is not a wall.

You already use several:

  • A fetch wrapper in a frontend that adds the auth header, parses JSON and maps errors.
  • The service layer in a web backend, where a controller calls orderService.place() and never sees the repositories.
  • Any cloud SDK. A single upload(file) call is a facade over signing, chunking, retrying and several HTTP requests.

Handling the failure path

The facade is also the right home for the “what if step two fails” logic, which is where hand written client code goes wrong most often.

class Inventory {
  reserve(dish: string): string {
    return `reserved ${dish}`
  }
 
  release(dish: string): string {
    return `released ${dish}`
  }
}
 
class Payments {
  charge(amount: number): string {
    if (amount > 5000) throw new Error('Card declined')
    return `charged ${amount}`
  }
}
 
class OrderFacade {
  constructor(
    private readonly inventory: Inventory,
    private readonly payments: Payments,
  ) {}
 
  placeOrder(dish: string, amount: number): string[] {
    const log = [this.inventory.reserve(dish)]
    try {
      log.push(this.payments.charge(amount))
    } catch {
      log.push(this.inventory.release(dish))
    }
    return log
  }
}
 
const orders = new OrderFacade(new Inventory(), new Payments())
 
console.log(orders.placeOrder('Family feast', 9000).join(', '))
// > reserved Family feast, released Family feast

The payment failed, so the stock went back. Three client teams did not each have to remember that.

The facade that ate the codebase

A facade grows. OrderFacade gets cancelOrder, then refund, then updateAddress, then exportMonthlyReport, and two years on it has 45 methods and every team edits it. That is the single responsibility problem again. Keep a facade to one use case or one closely related group, and add a second facade when a second group appears.

Business rules in the facade

A facade coordinates. It decides the order of calls and what to undo on failure. The rule for how much to charge belongs in Payments, and the rule for what counts as in stock belongs in Inventory. If you find if statements about prices inside the facade, a subsystem is missing a method.

Facade versus Adapter

Both sit in front of other code. An adapter makes one existing class fit an interface the caller already has, and it changes nothing about how much work the caller does. A facade defines a new interface, usually smaller, over several classes, so the caller does less. Adapter is about compatibility. Facade is about simplicity.

TypeScript you just picked up
constructor(private readonly inventory: Inventory, ...)
The facade is given its subsystems. One parameter per line once there are several.
placeOrder(...): string[]
A method returning an array of strings.
const log = [this.inventory.reserve(dish)]
The array type is inferred as string[] from its first element.
steps.join(', ')
Glue an array of strings into one string with a separator.
try { ... } catch { ... }
Run the fallback if anything inside throws. The error variable is optional.

Checkpoint

Checkpoint

1. After OrderFacade exists, can an admin tool still call Inventory.release() directly?

2. What is the main benefit of putting the reserve, charge, ticket, assign sequence in one facade method?

3. OrderFacade has grown to 45 methods covering orders, refunds, reports and address changes. What is the fix?

Say this in 60 seconds

A facade is one simple entry point in front of a set of classes that are complicated to use together. Placing an order might need inventory, payments, the kitchen and rider assignment called in a specific order, with stock released if the payment fails. A facade puts that sequence in a single method, so each client makes one call and cannot get the order wrong, and a new step is added in one place. It adds no new capability and it does not hide the subsystems, they can still be used directly. The risks are that it grows into a god object, so I keep one facade per use case, and that business rules creep into it, when it should only coordinate. Compared with an adapter: an adapter makes one class fit an existing interface, a facade makes many classes easier to use.

IndGeek provides solutions in the software field, and is a hub for ultimate Tech Knowledge.