Facade
One simple front door to a complicated subsystem.
01The intuition
The problem it solves
Buying a cinema ticket in the app's code takes six calls in the right order: check the seat, hold it, price it with discounts, charge the card, mark the seat sold, email the ticket. The first version does all six in the button handler.
def on_buy_clicked(user, show, seat_no, card):
seat = seats.get(show, seat_no)
if not seat.is_free(): raise ValueError("taken")
hold = holds.create(seat, user, minutes=5)
price = pricing.quote(show, seat, user.membership)
txn = payments.charge(card, price)
seats.mark_sold(seat, txn.id)
holds.release(hold)
emailer.send_ticket(user.email, show, seat, txn)
A second entry point, the web checkout, copies the six calls and forgets to release the hold. A third, the kiosk, charges before holding and sells a seat twice. The subsystem is correct; the callers are the problem, because each of them has to know the dance.
A facade is one class with a few simple methods that performs the dance correctly on behalf of callers. The subsystem stays as it is; callers talk to the front door.
Build it one piece at a time
Step 1: name the operations callers actually want. Not "hold a seat"; callers want buy_ticket and maybe cancel_ticket.
class BookingFacade:
def __init__(self, seats, holds, pricing, payments, emailer):
self._seats, self._holds = seats, holds
self._pricing, self._payments, self._emailer = pricing, payments, emailer
def buy_ticket(self, user, show, seat_no, card) -> "Ticket": ...
Step 2: move the dance inside, once. The order of calls and the cleanup live in one method that three entry points share.
class BookingFacade:
# constructor as in step 1
def buy_ticket(self, user, show, seat_no, card) -> "Ticket":
seat = self._seats.get(show, seat_no)
if not seat.is_free():
raise SeatTaken(seat_no)
hold = self._holds.create(seat, user, minutes=5)
try:
price = self._pricing.quote(show, seat, user.membership)
txn = self._payments.charge(card, price)
self._seats.mark_sold(seat, txn.id)
finally:
self._holds.release(hold)
self._emailer.send_ticket(user.email, show, seat, txn)
return Ticket(show, seat, txn)
Step 3: give callers only the facade. Button, web and kiosk each call one method. The forgotten-release bug cannot happen, because finally is in the one copy.
def on_buy_clicked(user, show, seat_no, card):
ticket = booking.buy_ticket(user, show, seat_no, card)
show_confirmation(ticket)
Step 4: keep the subsystem reachable for the rare case. A facade is a convenience, not a wall. An admin tool that needs to release a stuck hold can still use holds directly. Do not hide the parts; simplify the common path.
| Step | Copies of the six-step dance | Entry points that know about holds | Where "release the hold even on failure" lives |
|---|---|---|---|
| Start | 3, all slightly different | 3 | nowhere reliable |
| 2 and 3 | 1 | 0 | one finally block |
Why it works
The facade raises the level of the conversation. Callers say what they want in domain words, buy_ticket, and the facade knows how the subsystem's parts cooperate to deliver it. The invariant is "a complete, correctly ordered workflow is a single method". It also shrinks the surface callers depend on, so the subsystem's parts can be reorganised, a new pricing service, a different hold mechanism, without touching callers.
Where it breaks: the facade that grows into a god class with forty methods and all the business logic, because it is the easiest place to add code. Keep rules in the parts; keep orchestration in the facade. Also avoid a facade that re-exports every method of every part, which hides nothing and adds a hop.
The trade-off you must say out loud
A facade adds a layer and can hide useful capabilities if it is the only door. Say which workflows it covers and that the parts remain available. In an interview it is also the natural home for the sequence you walk through in the main flow, so name it as such: "the BookingService is the facade; here is the sequence inside buy_ticket". The follow-up is usually about failure halfway, and the answer is compensation inside the facade, which is exactly where you want that logic.
Where it shows up in the 100 Google problems
- Design a movie ticket booking system:
buy_ticketabove is the core flow; hold, price, charge and mark sold are the parts. - Design an ATM:
withdrawis a facade over the card reader, the bank connection and the cash dispenser, with the state machine deciding when it may be called. - Design an online shopping system:
place_ordercoordinates cart, inventory reservation, payment and order creation. - Design an in-memory database with transactions: the
Databaseobject is the facade over tables, indexes and the transaction log;executeis the one door.