Concepts / Structural patterns / Facade

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.

StepCopies of the six-step danceEntry points that know about holdsWhere "release the hold even on failure" lives
Start3, all slightly different3nowhere reliable
2 and 310one 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

02Used in these problems