Checking out of an online shop feels like one action. Behind the button, it’s pricing, validation, payment, stock, courier booking and notifications, in exactly the right order. Facade gives that whole process one simple front door, so the code that presses the button doesn’t have to know what’s behind it.

   
Category Structural
Smell fixed S10: The console coordinates every checkout subsystem
Code patterns/structural/facade

The Scene

The trading day has a run of failed checkouts before anything succeeds:

[052] checkout customer=cu-1 payment=card
  checkout rejected: out of stock KETTLE-1L
[054] checkout customer=cu-2 payment=card
  checkout rejected: address required
[056] checkout customer=cu-1 payment=card
  checkout rejected: fraud score 88 exceeds threshold
[059] checkout customer=cu-3 payment=card
  checkout rejected: total exceeds credit limit
[060] checkout customer=cu-1 payment=card
  card charged A$300.33 for Ava
  ShipFast booked SF-0001 for o-1001 (30033 cents)
  email to Ava: order o-1001 confirmed, total A$300.33

Every one of those outcomes depends on steps running in the right order. Validate before charging. Charge before booking a courier. Roll back reserved stock if something fails halfway. So who owns that order?


The Smell

In the baseline, the console does. StoreConsole is the class that reads scenario steps and prints the transcript, and it also runs the entire checkout sequence itself. Here’s the second half, with stock reservation and rollback:

        var order = orderService.Create(customer, cart, quote, paymentType);
        var reservedLines = new List<CartLine>();
        foreach (var line in order.Lines)
        {
            if (!inventory.ReserveStock(line.Product.Sku, line.Quantity))
            {
                foreach (var reservedLine in reservedLines)
                {
                    inventory.AdjustStock(reservedLine.Product.Sku, reservedLine.Quantity);
                }

                Log.Info("checkout rejected: stock changed for " + line.Product.Sku);
                return;
            }

            reservedLines.Add(line);
        }

The console talks to six different services directly. If a second part of the system ever needed to place an order (an API, a batch job, a test), it would have to copy this whole sequence, rollback logic included, and keep both copies in sync forever.


The Idea

A Facade puts one simple entry point in front of a group of services. It knows the sequence. The caller only knows the front door.

Think of a hotel concierge. You say “I’d like dinner and a taxi at seven.” You don’t phone the restaurant, check the taxi company’s availability and confirm both yourself. The concierge knows who to call and in what order. And if you ever want to phone the restaurant directly, you still can.

That last bit matters. A facade doesn’t lock the services away. It just means most callers never need to deal with them.


The Refactor

The whole sequence moves into a new class, CheckoutFacade, with one public method:

    public string PlaceOrder(Cart cart, Customer customer, string paymentType)
    {
        if (cart.Lines.Count == 0)
        {
            return "checkout rejected: cart is empty";
        }

        var quote = pricing.BuildQuote(cart, customer);
        var error = orderValidator.Validate(customer, cart, quote);
        if (error is not null)
        {
            return "checkout rejected: " + error;
        }

And the console’s checkout shrinks to this:

var customer = customers[cart.CustomerId];
Log.Info(checkoutFacade.PlaceOrder(cart, customer, Text(step, "payment")));

Find the customer, place the order, print the result. The console is back to doing its own job.

classDiagram
    StoreConsole --> CheckoutFacade : PlaceOrder
    CheckoutFacade --> PricingService : build quote
    CheckoutFacade --> OrderValidator : validate
    CheckoutFacade ..> IPaymentProcessor : select and charge
    CheckoutFacade --> InventoryService : reserve or restore stock
    CheckoutFacade --> ShipFastClient : book shipment
    CheckoutFacade --> OrderService : create and pay
    OrderService --> OrderConfirmedEmail : send confirmation
    OrderService --> OrderConfirmedSms : send confirmation

Smells travel with the code

If you open CheckoutFacade.cs, you’ll find two old friends inside it:

    // SMELL S03: payment processor construction is selected by a switch in checkout
    // SMELL S06: checkout calls the courier client with XML and parses its response directly

The payment switch and the courier XML moved into the facade, still marked. That’s deliberate. Factory Method and Adapter fix those in their own solutions, and the capstone shows all three working together.

A facade makes the process readable

Here’s a bonus I didn’t expect. With the whole sequence in one short method, you can actually read the checkout process. And reading it raises a question: the card is charged before stock is reserved. If a reservation failed after payment, the order is rejected, but nothing refunds the charge.

In practice the validator checks stock first, so it’s unlikely to bite. And a refactor must not change behaviour, so the golden master holds the order exactly as it was. But a business analyst could look at PlaceOrder and spot that question in a minute. In the baseline, it was buried in a 772-line console class.


What It Cost

4 files: about 246 lines added and 100 removed. More than half the additions are tests, because once checkout has a front door, you can test it directly: a valid cart is charged, reserved, shipped and confirmed, and a failed validation takes no payment.

That testability is often the biggest win of a facade.


When Not to Use It

  • The facade starts making business decisions. A facade should coordinate, not decide. If pricing rules, discounts and validation logic start piling up inside it, you’ve built a new god class with a nicer name.
  • Callers need different sequences. One facade per common workflow is great. One giant facade that tries to serve every caller with flags and options is not.
  • There’s only one service behind it. Wrapping a single service in a “facade” just adds a layer.

Easily Confused With

Mediator. Both sit in the middle of several objects. A facade is a front door for outside callers, and the services behind it don’t know it exists. A mediator coordinates peers that talk to each other through it. See Facade vs Mediator.


Try It Yourself

dotnet build patterns/structural/facade/Facade.slnx
dotnet test patterns/structural/facade/Facade.slnx
dotnet run --project patterns/structural/facade/src/CornerStore/CornerStore.csproj -- scenarios/trading-day.json

Read CheckoutFacade.PlaceOrder top to bottom, then try finding the same sequence in the baseline’s StoreConsole.cs. Same logic, very different reading experience.

The Facade README has a full sequence diagram of a checkout. The full change is in DIFF.md.


Previous: Decorator · Next: Flyweight