Skip to content

Facade

One simple entry point over a complicated subsystem — what it legitimately hides, and how it turns into a god object if you let it.

Beginner17 minUpdated 8 August 2026

Facade gives a subsystem one simple front door. It is the easiest pattern to apply and the easiest to overgrow: the same instinct that produces a helpful entry point produces, three years later, a 2,000-line class that everything depends on.

The distinction that keeps it healthy: a facade simplifies access to a subsystem; it does not become the subsystem.

The problem

Placing an order legitimately involves several collaborators. Every caller currently orchestrates them.

// In the web controller, and again in the admin tool, and again in the CLI importer
const validated = validator.check(cart);
const priced = pricing.calculate(validated, customer);
const reserved = await inventory.reserve(priced.items);
const payment = await gateway.charge(priced.total, customer.paymentMethod);
const order = await orders.create({ priced, reserved, payment });
await notifications.orderPlaced(order);

Three callers now know the sequence, and the sequence is a rule — reserve before charging, notify after persisting. Duplicating it means three chances to get the order wrong and three places to edit when a step is added.

class OrderPlacement {
  async place(cart, customer) { /* the sequence, once */ }
}
Conceptual diagramWeb controllerOrderPlacementfacadeAdmin toolImporterValidatorPricingInventoryPayment gatewayOrder repositoryNotifications
Diagram, as text
  • Web controller leads to OrderPlacement facade
  • Admin tool leads to OrderPlacement facade
  • Importer leads to OrderPlacement facade
  • OrderPlacement facade leads to Validator
  • OrderPlacement facade leads to Pricing
  • OrderPlacement facade leads to Inventory
  • OrderPlacement facade leads to Payment gateway
  • OrderPlacement facade leads to Order repository
  • OrderPlacement facade leads to Notifications

What a facade is for, precisely

It removes duplicated orchestration. The value is that the sequence exists once, not that the call is shorter.

It names an operation in domain terms. placeOrder is a thing the business does. Six calls in a row is not.

It reduces the surface callers depend on. A controller that knew six collaborators now knows one, so refactoring any of the six does not ripple outwards.

It does not restrict access. This matters and is often misunderstood: a facade is a convenience, not a wall. If the underlying components remain public, callers may still use them directly — and sometimes should, for cases the facade does not cover. A facade that must be the only route is enforcing a boundary, which is a module or package concern rather than a class concern.

How it turns into a god object

The failure is gradual and each step is reasonable.

  1. OrderPlacement.place() — clearly good.
  2. Someone needs to place an order without notifying. A boolean parameter appears.
  3. Someone needs to cancel an order. It goes here too — it is order-related.
  4. Refunds, partial refunds, address changes, re-sends. All order-related.
  5. The class is 2,000 lines, depends on eleven collaborators, and every feature touches it.

The failure is cohesion, not size. A facade should be defined by one entry point into a subsystem, not by a topic. "Everything about orders" is a topic, and topics grow without limit.

Two signals that it has happened:

  • Boolean parameters that skip steps. place(cart, customer, { notify: false }) means the facade is serving two different operations and disagreeing with itself about what the sequence is.
  • Collaborators used by only one method. If eight of eleven dependencies are needed by one method each, the class is a namespace, not an object.

Facade, service layer and adapter

These three overlap enough to be worth separating.

WrapsIntentInterface
Facadea subsystem you ownmake the common case easynew and simpler
Adaptersomething foreignmake it fit a contract you already requirepre-existing
Service layerthe domaindefine the application's transaction boundary and use casesthe application's own

In practice an application service and a facade often look identical. The distinction worth keeping is that a service layer method is a use case with a transaction boundary and authorisation — it is where the application decides something — whereas a facade is a convenience over calls that could have been made directly.

When you do not need one

One caller. A facade over a sequence used in exactly one place is a function extracted for readability, which is good, but does not need a pattern name or its own class.

The sequence is not a rule. If callers legitimately vary the order or skip steps, freezing it into a facade will produce the boolean parameters described above.

The subsystem is already simple. Wrapping two calls in one saves a line and adds a file.

Check your understanding

Common mistakes

Organising by topic. "Everything about orders" grows forever; "one way into the order subsystem" does not.

Boolean parameters that skip steps. Two operations wearing one name.

A facade that everything imports. Coupling has been centralised, not reduced.

Assuming it enforces a boundary. A facade is a convenience; enforcement is a module or package concern.

A method per variation. Cover the common case and let unusual callers use the components underneath.

Wrapping something already simple. Two calls behind one is a file for a line.

Putting the domain rules in the facade rather than the domain. Orchestration is not the same as policy, and a facade that decides is a service layer that has not admitted it.

Practice

A `NotificationFacade` has 14 methods — sendWelcome, sendPasswordReset, sendOrderConfirmation, sendShippingUpdate and so on — each assembling a template, resolving preferences and dispatching. It is imported by 40 modules. Diagnose it and propose a design.

Write your own answer first, on paper or in a note. Then compare.

Show a hint

Ask what varies between the 14 methods and what does not.

Show a strong answer

Diagnose by separating what varies from what does not. Every method does the same three things: choose a template, resolve the recipient's channel preferences, dispatch. What varies is the template identifier and the data. That is one operation with a parameter, expressed as fourteen methods — which is why the class grows by one method per feature forever and why 40 modules depend on it.

The 40 importers are the more serious symptom. Each one depends on a class that will change every time any other feature adds a notification. Nothing in the shape of the dependency reflects what the caller actually uses, so every change has a blast radius of 40.

What I would build:

  • One operation, parameterised: notifier.send(recipient, notification), where a notification is a small value carrying its type and data. Adding a notification type stops touching the notifier at all — it adds a template and a value type.
  • Templates in data, not in methods. A registry keyed by type, so the fourteen methods collapse into fourteen entries that a non-engineer could plausibly review.
  • Preference resolution stays inside, because it is the genuine common logic and the reason a shared component exists at all.

Why this is better than splitting into fourteen small classes, which is the other obvious answer: the fourteen are not really different operations. Splitting them would preserve the accidental structure while multiplying the files. The right split is between the mechanism (resolve, render, dispatch), which is one thing, and the catalogue of notifications, which is data.

How I would migrate 40 importers:

  • Add the new method alongside the old ones and implement the old ones in terms of it, so nothing breaks and the new path is exercised immediately.
  • Forbid new methods on the facade by review or lint, which stops the growth on day one — this is the change that matters most.
  • Convert callers opportunistically, and delete each old method when its last caller goes.

What I would check before starting: whether any of the fourteen genuinely differs — one that must be synchronous, or transactional with a database write, or legally required to be delivered. Those are not the same operation and should not be forced into the uniform path; discovering them late is how this kind of refactor produces an outage.

Two engineers disagree: one says the new `CheckoutFacade` should be the only permitted way to reach the pricing, inventory and payment components; the other says callers should be free to use them directly. Resolve it.

Write your own answer first, on paper or in a note. Then compare.

Show a hint

Separate the question of what should be encouraged from what should be enforced, and ask what enforcement mechanism is actually available.

Show a strong answer

Both are half right, and the disagreement is really about two different questions being asked at once.

On enforcement: a class cannot do it. If the components are importable, they will be imported, and a facade is a convenience rather than a wall. If the team genuinely wants "only through here", the mechanism is a module boundary — package-private visibility, an index that exports only the facade, or a lint rule on import paths. Arguing about the facade is arguing about the wrong artefact.

On whether it should be enforced: it depends on what the facade guarantees.

  • If the sequence encodes correctness — reserve before charging, never charge without reserving — then yes, enforce it. A caller that gets the order wrong causes a real defect, and hoping people read the documentation is not a control. Here I would make the components internal to the module and export only the facade.
  • If the facade is a convenience over independently sensible components, no. A reporting query that needs pricing without inventory is legitimate, and forcing it through checkout will produce a facade method called priceOnlyForReports — the first step towards the god object.

How I would resolve it concretely: ask the first engineer to name a specific incorrect sequence a caller could write and what it would cost. If they can — double charging, or charging without reserving stock — the answer is enforcement, and the discussion moves to module boundaries. If they cannot name one, the desire is for consistency rather than correctness, and consistency is better served by making the facade the obvious, documented, easiest path than by prohibition.

The middle position I would actually propose: enforce the boundary around the dangerous component only. Payment is the one where an out-of-order call costs money, so make the gateway reachable only through checkout. Pricing and inventory stay open, because reading them independently is useful and harmless.

What would change my mind later: a bug caused by direct use. That is evidence, and it converts the argument from preference into a decision with a reason attached.

Key takeaways

  • Facade provides one simple entry point to a subsystem; its value is that a multi-step sequence exists once, not that calls are shorter.
  • It is a convenience, not a wall. Enforcing a boundary is a module or package concern.
  • Define it by one entry point into a subsystem, never by a topic — "everything about orders" has no natural limit.
  • Boolean parameters that skip steps mean two operations are hiding in one method.
  • A facade imported by everything has centralised coupling, not reduced it. Split by operation.
  • Distinguish it from Adapter (fits a pre-existing contract) and a service layer (use cases with transaction boundaries and authorisation).
  • Skip it when there is one caller, when the sequence is not a rule, or when the subsystem is already simple.

Further reading

  • Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1994).
  • Martin Fowler, Patterns of Enterprise Application Architecture (Addison-Wesley, 2002) — the service layer and its relationship to facades.
  • Eric Evans, Domain-Driven Design (Addison-Wesley, 2003) — application services and module boundaries.
  • Robert C. Martin, Clean Architecture (Prentice Hall, 2017) — use cases as the application's entry points.
  • Kent Beck, Implementation Patterns (Addison-Wesley, 2007) — on cohesion as the organising principle for classes.

Check yourself

3 questions in this lesson. Answer each once to complete it.

No problem practices this lesson yet. Browse the practice problems

Next lessonProxy
Lessons in this track