Skip to content

11. Integrating and Simplifying: Adapter and Façade

These are the two patterns you and your agent will reach for most, because most real work is integration: making someone else’s code fit yours, and taming a subsystem that has grown too many moving parts. Adapter and Façade are both about wrapping — one to make code fit, the other to make a subsystem simple. The discipline that keeps both honest is a single question: does the wrapper hide more than it shows?

checkout-lite has two integration pains, and they are the everyday texture of modern coding.

First, payments. Chapters 8 and 10 built our own gateways behind IPaymentGateway, whose contract is charge(amount: float) -> Receipt. Now the business wants Stripe, and the SDK looks nothing like ours:

class StripeClient: # third-party — we don't own a line of it
def create_charge(self, amount_cents: int, currency: str = "usd") -> dict: ...

Different method name, money in cents, a raw dict / Record back. We can’t make their class satisfy our interface by inheritance, and we really don’t want cents-and-records leaking into pricing, receipts, and refunds across the codebase.

Second, the checkout flow itself. Placing an order now means orchestrating four subsystems in the right order — price it, charge it, notify the customer, save it — and every caller (the web handler, the admin tool, the test fixture) has to know the whole dance. The complexity is real, but no caller should have to learn it to place one order.

The Adapter pattern puts a translating layer between an interface you expect (the target) and an object that doesn’t match it (the adaptee). The adapter implements the target interface and, inside each method, delegates to the adaptee while translating the mismatch — names, types, units, return shapes.

classDiagram
    class IPaymentGateway {
        <<interface>>
        +charge(amount) Receipt
    }
    class StripeGateway {
        +charge(amount) Receipt
    }
    class StripeClient {
        +create_charge(cents) dict
    }
    IPaymentGateway <|.. StripeGateway
    StripeGateway o--> StripeClient : translates to

The rest of checkout-lite keeps talking to IPaymentGateway; only StripeGateway ever sees Stripe. That is the adapter earning its keep — it doesn’t just rename a method, it translates dollars to cents and a dict to a Receipt. An adapter that only forwarded a call unchanged would be a pass-through (Chapter 9), not a design.

The Façade pattern puts one simple interface in front of a whole subsystem, coordinating its parts so callers don’t have to. It doesn’t change what the subsystem can do; it changes how much you must know to use it.

classDiagram
    class place_order {
        +place_order(order) Receipt
    }
    class pricing
    class gateway
    class notify
    class persistence
    place_order ..> pricing
    place_order ..> gateway
    place_order ..> notify
    place_order ..> persistence

This is, in John Ousterhout’s terms, a depth-adding move: a tiny interface (place_order) over real functionality (four coordinated subsystems). A façade earns its place by depth — if it merely forwarded each subsystem call one-to-one without coordinating or simplifying, it would be another pass-through.

The SDK’s shape leaks everywhere a payment happens:

# The SDK's shape leaks everywhere a payment happens:
client = StripeClient()
resp = client.create_charge(int(order_total * 100)) # remember: cents!
if resp["paid"]:
confirmation = resp["id"] # dig through the dict
# ...and the same cents-and-dict ritual is copied into refunds, retries, admin...

The adapter implements our interface and translates inside, so Stripe stays invisible everywhere else:

class StripeGateway(IPaymentGateway):
def __init__(self, client: StripeClient) -> None:
self._client = client
def charge(self, amount: float) -> Receipt:
resp = self._client.create_charge(int(round(amount * 100))) # dollars → cents
return Receipt("stripe", amount, resp["id"]) # dict → Receipt
# everywhere else, Stripe is invisible:
gateway: IPaymentGateway = StripeGateway(StripeClient())
receipt = gateway.charge(order_total)

Every caller orchestrates the whole subsystem by hand:

# every caller orchestrates the whole subsystem by hand:
total = order_total(order)
confirmation = charge(total)
send_receipt(order, total)
save_order(order, total)
receipt = Receipt(total, confirmation)

One friendly door coordinates the four steps; callers learn only the door:

def place_order(order: Order) -> Receipt: # the one friendly door
total = order_total(order)
confirmation = charge(total)
send_receipt(order, total)
save_order(order, total)
return Receipt(total, confirmation)
receipt = place_order(order) # callers know only this

The full code, with tests that prove the adapter translates and the façade drives the whole flow in one call, is in examples/ch11/py/ (Python) and examples/ch11/ts/ (TypeScript).

They are constantly confused, because both wrap. The difference is what they change:

AdapterFaçade
ChangesThe shape of an interfaceThe amount you must know
FunctionalitySame — just made to fitSame — just made simple
WrapsUsually one adapteeA whole subsystem
One-liner”Make it fit""Make it simple”

If you’re matching a square peg to a round hole, it’s an Adapter. If you’re putting a reception desk in front of a busy office, it’s a Façade. And both face the same test: the wrapper must hide more than it exposes — otherwise it’s a pass-through with a pattern’s name on it.

The two languages reach the same design from different starting points — and where they differ is exactly the lesson about what the pattern really is.

Here is where the book’s interface rule (Chapter 8) pays off. You own your gateways, so they inherit the IPaymentGateway ABC. But the third-party SDK can’t inherit anything of yours — so you depend on a Protocol, which matches by shape instead of by name:

@runtime_checkable
class SupportsCharge(Protocol):
def charge(self, amount: float) -> Receipt: ...

This buys two things. If a foreign object already has the shape you need, it satisfies the Protocol with no adapter at all — structural typing plus duck typing. And when it doesn’t (Stripe’s create_charge is not charge), your adapter conforms to the shape without nominal inheritance, and a type checker verifies it. You control both sides → ABC; you’re retrofitting code you don’t → Protocol.

For Façade, the Pythonic form is often not a class at all. A module is a façade: a module-level place_order() function is “one friendly door,” and a curated __init__.py that re-exports a small public API is a façade over a whole package (more on that in Chapter 16). Reach for a Façade class only when the simplified interface needs to hold state across calls.

Gluing libraries together is the single most common thing an agent does, which makes these the patterns it reaches for most — and the ones it most often builds as pass-throughs. Your review job is to check for translation and depth, not just for a wrapper.

You sayThe agent hears
”Adapt this SDK to our interface”Implement our target interface; translate to the adaptee inside
”Depend on a structural shape (Protocol / interface)“Don’t inherit their class; match by shape, verified by the type checker
”Put a Façade over this subsystem”One simpler entry point that coordinates the parts
”One entry point”Callers should learn one call, not the orchestration
”Does this wrapper add depth?”Reject pass-throughs that rename or forward 1:1
💬 Adapt a third-party SDK

Wrap this third-party [SDK] behind our [IPaymentGateway] with an adapter. The adapter implements our interface and translates to the SDK (names, units, return types) so the SDK’s shape never appears elsewhere. Depend on a structural shape (a Protocol in Python, a plain interface in TypeScript) rather than making their class inherit ours. Don’t add a layer that only renames.

💬 Façade over a subsystem

These call sites orchestrate [pricing/gateway/notify/persistence] by hand. Add a Façade — a single place_order(order) function (a module-level function, not a class, unless it must hold state) that coordinates them and returns the result. It should reduce what a caller must know, not re-expose every subsystem method.

  • Does the adapter translate (names, units, shapes), not just rename?
  • For third-party code, does it depend on a structural shape (Protocol / interface) rather than inheriting?
  • Are you adapting code you don’t own? (If you own it, change it instead.)
  • Does the façade simplify — fewer, cleaner calls — rather than forward 1:1?
  • Does each wrapper hide more than it exposes (Ousterhout’s depth test)?
  • The pass-through wrapper. An “adapter” or “façade” that forwards calls unchanged — ceremony with no depth. The most common failure for both patterns.
  • Inheritance for foreign code. The agent tries to make a third-party class inherit your base class (it can’t, cleanly) instead of depending on a structural shape (a Protocol / interface).
  • The leaky façade. A façade whose signature re-exposes the whole subsystem — a directory, not a door.
  • Adapting your own code. Wrapping code it could simply edit, adding a layer for nothing.
  • Adapter changes an interface’s shape to fit what a client expects; Façade reduces how much a client must know about a subsystem. Make it fit vs. make it simple.
  • Adapter is for code you don’t own and must translate — names, units, return shapes. This is where structural typing shines (a Protocol in Python, a plain interface in TypeScript): depend on a shape, and a foreign class that already matches needs no adapter at all.
  • The lightweight Façade is often a module-level function or a curated barrel module (__init__.py / index.ts), not a class.
  • Both collapse into Chapter 9’s pass-through when they add no depth. Ousterhout’s test decides it: a wrapper must hide more than it exposes.
  • These are the AI era’s workhorse patterns — and the ones agents most often build as ceremony. Review for translation and depth.
  • Glossary terms added: Adapter · Façade · Protocol (structural typing).