Card 04/ 08
Facade Example: Placing an Order Across Stock, Payment and Shipping
place_order() checks stock, charges the card and books a courier, so the web handler calls one method.
Placing an order is a natural Facade, and this example maps the template onto it. A shop's checkout handler needs three services in order: reserve the stock, charge the card, book a courier. The sequence has one rule that is easy to forget, release the stock when the payment is declined, and the facade is the one place that rule is written.
| Template | Order example |
|---|---|
| Facade | OrderFacade, with place_order |
| Subsystem classes | Inventory, PaymentService and Shipping |
| Client | The web handler that receives the order form |
class Inventory:
def __init__(self, stock: dict[str, int]) -> None:
self._stock = stock
def reserve(self, sku: str, quantity: int) -> bool:
if self._stock.get(sku, 0) < quantity:
return False
self._stock[sku] -= quantity
print(f"inventory: reserved {quantity} x {sku}")
return True
def release(self, sku: str, quantity: int) -> None:
self._stock[sku] += quantity
print(f"inventory: released {quantity} x {sku}")
class PaymentService:
def charge(self, card: str, pence: int) -> bool:
approved = not card.endswith("0000")
print(f"payment: {pence} pence on card ending {card[-4:]}, {'approved' if approved else 'declined'}")
return approved
class Shipping:
def book(self, sku: str, address: str) -> str:
print(f"shipping: courier booked for {sku} to {address}")
return "SHIP-7781"
class OrderFacade:
def __init__(self, inventory: Inventory, payments: PaymentService, shipping: Shipping) -> None:
self._inventory = inventory
self._payments = payments
self._shipping = shipping
def place_order(self, sku: str, quantity: int, card: str, pence: int, address: str) -> str:
if not self._inventory.reserve(sku, quantity):
return "out of stock"
if not self._payments.charge(card, pence):
self._inventory.release(sku, quantity)
return "payment declined"
return self._shipping.book(sku, address)
shop = OrderFacade(Inventory({"LAMP-01": 3}), PaymentService(), Shipping())
print(shop.place_order("LAMP-01", 1, "4111111111111111", 3999, "Leeds"))
print(shop.place_order("LAMP-01", 1, "4000000000000000", 3999, "York"))inventory: reserved 1 x LAMP-01
payment: 3999 pence on card ending 1111, approved
shipping: courier booked for LAMP-01 to Leeds
SHIP-7781
inventory: reserved 1 x LAMP-01
payment: 3999 pence on card ending 0000, declined
inventory: released 1 x LAMP-01
payment declinedThe first order reserves a lamp, charges the card and returns a shipment reference. The second card is declined, so the facade releases the reserved lamp before it reports the failure, and stock is back where it started. Without the facade, every handler that places an order would need to remember that release, and the one that forgot would lose stock.
The facade holds the order of calls and the clean-up between them, and nothing about stock levels, card checks or couriers. Those stay in the three services, where a stock report or a refund can use them without going through place_order.
← → move through sections and cards