Provide a unified, higher-level interface to a set of interfaces in a subsystem, making the subsystem easier to use without hiding it from anyone who needs the detail.
01
The problem Facade solves
The problem
Placing an order in your shop takes six calls, in a specific order, and everyone who places one has to know all six: reserve the stock, price the basket with tax and the current promotion, take the payment, hand the pick list to the warehouse, send the confirmation email, then record the conversion for analytics.
The web controller does it. So does the mobile API. So does the admin "place this order for the customer" screen, the CSV importer, and two integration tests. Six copies of the same choreography.
Then the rules change: payment now has to happen after the fraud check, and the fraud check is a new seventh service. You have to find all six copies. You will find five, and the CSV importer will quietly keep charging cards before checking for fraud until somebody notices in three weeks.
02
How the Facade pattern works
Write one class whose only job is to know the choreography, and give it a method that reads like the thing you actually wanted to do:
The facade offers a small, task-shaped interface — placeOrder(cart, card), not six primitives in the right order.
It holds the subsystem objects and calls them in the correct sequence, handling the ordering, the roll-back and the plumbing between them.
Callers depend on the facade only. One import instead of six, and the sequence lives in exactly one file.
The subsystem stays public. Anyone with an unusual need can still reach past the facade — it is a convenient door, not a wall.
That last point is the one people get wrong. GoF is explicit that a facade does not forbid access to the subsystem, and a facade that is the only way in stops being a facade and becomes a bottleneck: every unusual requirement has to be added to it, it grows a parameter for each caller's special case, and within a year it is the file everyone edits and nobody understands. Make the common path easy; leave the uncommon path possible.
Participants. There are only two: the Facade, which knows the subsystem and the order to call it in, and the subsystem classes, which know nothing about the facade at all. That one-way ignorance matters — the subsystem must stay usable on its own, which is what keeps the facade optional rather than mandatory.
03
See it: one call, five underneath
Place an order through the facade, then remove it and place the same order again. Watch the second counter — the number of classes the caller has to know about — because that number is the whole reason this pattern exists.
▶ Try it — place, cancel, track
An interactive call tracer: CheckoutController calls placeOrder() on the OrderFacade, which fans it out into five ordered calls across InventoryService, PricingEngine, PaymentGateway, WarehouseClient and Notifier. Remove the facade and the controller must make all five itself, in the right order, and depend on all five classes.
Cancelling is the interesting one. Refund then release stock — do it the other way round and a race lets someone buy the item you haven't refunded yet. With the facade, that ordering is decided once, in one file, with a comment explaining why. Without it, it is a piece of folklore living in five controllers.
04
Facade pattern code examples
One task-shaped method; the sequence, the roll-back and the six imports live behind it.
public final class OrderFacade {
private final InventoryService inventory;
private final PricingEngine pricing;
private final PaymentGateway payments;
private final WarehouseClient warehouse;
private final Notifier notifier;
// Constructor omitted — five fields, injected.
/** The whole point: one method shaped like the thing the caller wanted. */
public Order placeOrder(Cart cart, Card card) {
Reservation reservation = inventory.reserve(cart.lines());
try {
Quote quote = pricing.quote(cart, cart.customer());
Receipt receipt = payments.charge(quote.total(), card);
Order order = Order.of(cart, quote, receipt);
warehouse.dispatch(order);
notifier.orderPlaced(order);
return order;
} catch (PaymentDeclined | WarehouseUnavailable e) {
// The compensating action belongs here too. This is exactly the line
// every hand-rolled copy of the sequence forgets.
inventory.release(reservation);
throw e;
}
}
}
// The controller shrinks to one line and one import:
//
// @PostMapping("/orders")
// Order create(@RequestBody Cart cart, Card card) {
// return orders.placeOrder(cart, card);
// }
//
// Note what the facade does NOT do: no pricing rules, no retry policy, no tax
// logic. It sequences, it compensates, it stops. The moment business rules move
// in, you have a god object wearing a pattern's name.
"""orders/__init__.py — in Python the facade is usually the MODULE, not a class.
Callers write `from shop import orders; orders.place_order(cart, card)`. The five
subsystem modules are imported here and nowhere else, which is the same
dependency win a facade class buys you in Java, with no class to instantiate.
"""
from shop import inventory, notifier, payments, pricing, warehouse
__all__ = ["place_order", "cancel_order", "track_order"]
def place_order(cart: Cart, card: Card) -> Order:
reservation = inventory.reserve(cart.lines)
try:
quote = pricing.quote(cart, cart.customer)
receipt = payments.charge(quote.total, card)
order = Order.of(cart, quote, receipt)
warehouse.dispatch(order)
notifier.order_placed(order)
return order
except (PaymentDeclined, WarehouseUnavailable):
inventory.release(reservation)
raise
# `__all__` is doing real facade work here: it declares the small public surface
# and keeps the five subsystem names out of `from orders import *`. If you need
# dependency injection for tests, take a class instead — but for most Python
# codebases the module IS the pattern, already built into the language.
// order_facade.h — note how few headers this exposes. In C++ a facade buys you
// something the other languages don't care about: COMPILE TIME. Callers include
// one small header instead of five heavy subsystem ones.
#include <memory>
class Cart;
class Card;
class Order;
class OrderFacade {
public:
OrderFacade();
~OrderFacade(); // out-of-line: Impl is incomplete here
Order placeOrder(const Cart& cart, const Card& card);
void cancelOrder(OrderId id);
private:
struct Impl; // pimpl — the subsystem types live in the .cpp
std::unique_ptr<Impl> impl_;
};
// ── order_facade.cpp ────────────────────────────────────────────────────────
#include "order_facade.h"
#include "inventory_service.h"
#include "pricing_engine.h"
#include "payment_gateway.h"
#include "warehouse_client.h"
#include "notifier.h"
struct OrderFacade::Impl {
InventoryService inventory;
PricingEngine pricing;
PaymentGateway payments;
WarehouseClient warehouse;
Notifier notifier;
};
Order OrderFacade::placeOrder(const Cart& cart, const Card& card) {
auto reservation = impl_->inventory.reserve(cart.lines());
try {
auto quote = impl_->pricing.quote(cart, cart.customer());
auto receipt = impl_->payments.charge(quote.total(), card);
Order order{cart, quote, receipt};
impl_->warehouse.dispatch(order);
impl_->notifier.orderPlaced(order);
return order;
} catch (...) {
impl_->inventory.release(reservation); // compensate, then rethrow
throw;
}
}
public interface IOrderService
{
Task<Order> PlaceOrderAsync(Cart cart, Card card, CancellationToken ct = default);
}
// The facade takes an interface of its own, purely so controllers can be tested
// against a fake. The pattern doesn't require it; testability does.
public sealed class OrderFacade(
IInventoryService inventory,
IPricingEngine pricing,
IPaymentGateway payments,
IWarehouseClient warehouse,
INotifier notifier) : IOrderService
{
public async Task<Order> PlaceOrderAsync(Cart cart, Card card, CancellationToken ct = default)
{
var reservation = await inventory.ReserveAsync(cart.Lines, ct);
try
{
var quote = await pricing.QuoteAsync(cart, cart.Customer, ct);
var receipt = await payments.ChargeAsync(quote.Total, card, ct);
var order = Order.Of(cart, quote, receipt);
await warehouse.DispatchAsync(order, ct);
await notifier.OrderPlacedAsync(order, ct);
return order;
}
catch (Exception) when (await Compensate(reservation, ct))
{
throw; // filter returns false; it ran the roll-back on the way past
}
}
}
// A facade is a natural fit for one DI registration that hides five:
// services.AddScoped<IOrderService, OrderFacade>();
//
// Worth naming the boundary: MediatR handlers are often described as facades.
// They aren't — a Mediator's participants talk THROUGH it to each other, while a
// facade's subsystem doesn't know it exists.
// orders.js — the module's exports are the facade. The five subsystem modules
// are imported here and, ideally, nowhere else in the app.
import * as inventory from './inventory.js'
import * as pricing from './pricing.js'
import * as payments from './payments.js'
import * as warehouse from './warehouse.js'
import * as notifier from './notifier.js'
export async function placeOrder(cart, card) {
const reservation = await inventory.reserve(cart.lines)
try {
const quote = await pricing.quote(cart, cart.customer)
const receipt = await payments.charge(quote.total, card)
const order = { ...cart, quote, receipt, id: receipt.reference }
await warehouse.dispatch(order)
await notifier.orderPlaced(order)
return order
} catch (err) {
await inventory.release(reservation)
throw err
}
}
export async function cancelOrder(orderId) { /* refund, then release */ }
// The `index.js` barrel file every JavaScript project grows is this pattern,
// usually by accident: one import path, a curated surface, the internals free to
// move. It stops being a facade and becomes dead weight when it re-exports
// everything — a facade is defined by what it leaves out.
// Package orders is the facade. In Go the unit of encapsulation is the package,
// so the exported functions ARE the simplified interface and the subsystem
// clients stay unexported.
package orders
type Service struct {
inventory *inventory.Client
pricing *pricing.Engine
payments payments.Gateway
warehouse *warehouse.Client
notifier notifier.Sink
}
func (s *Service) Place(ctx context.Context, cart Cart, card Card) (Order, error) {
res, err := s.inventory.Reserve(ctx, cart.Lines)
if err != nil {
return Order{}, fmt.Errorf("reserve: %w", err)
}
// No exceptions, so the compensating action is a defer with a named return —
// the Go idiom for "undo this unless everything worked".
var order Order
defer func() {
if err != nil {
s.inventory.Release(ctx, res)
}
}()
quote, err := s.pricing.Quote(ctx, cart)
if err != nil {
return Order{}, fmt.Errorf("quote: %w", err)
}
receipt, err := s.payments.Charge(ctx, quote.Total, card)
if err != nil {
return Order{}, fmt.Errorf("charge: %w", err)
}
order = New(cart, quote, receipt)
if err = s.warehouse.Dispatch(ctx, order); err != nil {
return Order{}, fmt.Errorf("dispatch: %w", err)
}
s.notifier.OrderPlaced(ctx, order)
return order, nil
}
// The interface is the facade's contract, and keeping it small is the design work.
interface OrderService {
placeOrder(cart: Cart, card: Card): Promise<Order>
cancelOrder(orderId: OrderId): Promise<void>
trackOrder(orderId: OrderId): Promise<Tracking>
}
export class OrderFacade implements OrderService {
constructor(
private readonly inventory: InventoryService,
private readonly pricing: PricingEngine,
private readonly payments: PaymentGateway,
private readonly warehouse: WarehouseClient,
private readonly notifier: Notifier,
) {}
async placeOrder(cart: Cart, card: Card): Promise<Order> {
const reservation = await this.inventory.reserve(cart.lines)
try {
const quote = await this.pricing.quote(cart, cart.customer)
const receipt = await this.payments.charge(quote.total, card)
const order = Order.of(cart, quote, receipt)
await this.warehouse.dispatch(order)
await this.notifier.orderPlaced(order)
return order
} catch (err) {
await this.inventory.release(reservation)
throw err
}
}
}
// A typed facade gives you a cheap architectural test: the day someone adds a
// sixth subsystem call to a controller, it won't type-check against
// OrderService, and the review catches it instead of production.
Read across the tabs: Python, JavaScript and Go barely need a class — a module or package with a small exported surface is already a facade, and __all__, a barrel file and unexported types are the language's way of spelling it. C++ gets a benefit the others don't have: putting the subsystem headers behind a pimpl means callers stop recompiling when a subsystem header changes. Java and C# use a class mostly so the five dependencies can be injected and faked in tests.
05
How to implement Facade
Find the sequence that keeps getting copied. The facade's methods should be named after the task (placeOrder), never after the mechanics (reserveAndChargeAndDispatch).
Create one class — or, in Python, Go and JavaScript, one module — that holds the subsystem objects and performs the sequence.
Move the ordering, the roll-back and the glue in. Leave the business rules out: a facade that starts calculating tax has stopped being a facade.
Return your own result type, not a tuple of subsystem objects, so callers still don't need the subsystem's vocabulary.
Keep the subsystem public. If a caller genuinely needs something the facade doesn't offer, they should reach past it rather than force a new parameter into it.
Point the existing callers at the facade and delete their copies of the sequence. If a copy has to stay, find out why — that difference is usually a bug.
06
When to use Facade — and when not to
Use it when a subsystem is genuinely complicated and most callers only ever want the same one or two journeys through it; when the same call sequence is duplicated across several entry points; when you want a layer boundary you can actually enforce; or when you need to keep a big dependency — a legacy module, a vendor SDK, an entire library — reachable from one file instead of two hundred.
Where it goes wrong
The god object. Every new requirement gets one more method, and three years later OrderFacade is four thousand lines with forty dependencies. A facade should stay thin; when it grows, split it by journey rather than adding to it.
The mandatory facade. Making the subsystem package-private so everything must go through the door converts a convenience into a bottleneck, and every edge case becomes a new boolean parameter. Simplify the common path; leave the rest reachable.
The pass-through. A facade whose methods each call exactly one subsystem method, with the same name and arguments, adds an indirection and nothing else. If there is no sequencing, no compensation and no simplification, delete it.
Business logic creep. Ordering and roll-back belong in a facade. Pricing rules, retry policies and tax do not — those belong in the subsystem, where they can be tested without five fakes.
You want to…
Use
Because
Give many classes one simple, task-shaped entry point
Facade
A new, smaller interface over a subsystem you chose to simplify.
Make one incompatible class fit an interface you already call
A mediator's colleagues talk through it; a facade's subsystem doesn't know it exists.
07
Quick check
🧠 Quick check
A reviewer suggests making all five subsystem classes package-private so that every caller is forced to go through OrderFacade. Good idea?
GoF is explicit that a facade doesn't prevent access to the subsystem for clients that need it. Sealing the subsystem sounds like discipline, but it means the reporting job that only needs a price quote must now call OrderFacade, so someone adds quoteOnly(cart), then placeOrder(cart, card, skipEmail), and the thin door becomes the widest class in the codebase. Make the common journey trivial, and let the rare caller reach past. If a sequence must never be bypassed — money, audit, compliance — that's a job for a transactional service boundary, not for hiding classes.
In the wild
Javajavax.faces.context.FacesContext and the JDBC DriverManager; Spring's JdbcTemplate is a facade over the raw JDBC connection, statement and result-set dance.
Pythonrequests — requests.get(url) is a facade over urllib3's connection pools, retries, encoding and cookie handling. The library's own tagline is essentially the pattern's intent.
C#HttpClient over SocketsHttpHandler, and File.ReadAllText() hiding the stream, reader and encoding detection.
Gohttp.Get() — a package-level facade over DefaultClient, its transport, and the request it builds for you.
JavaScriptfetch() over XHR and the browser's networking stack; and every index.js barrel that curates a package's public surface.
C++std::filesystem::copy_file hides platform APIs, and the pimpl idiom itself is a facade adopted for compile-time isolation.
What is the difference between the Facade and Adapter patterns?
Adapter is forced on you: some class has the wrong interface and your code cannot call it, so you write a translator to a shape that already exists. Facade is chosen by you: everything is perfectly callable, just tedious, so you invent a new and smaller interface over several classes. Rough test — an adapter usually wraps one object and changes its interface; a facade usually wraps several and reduces how many calls you make. Delete an adapter and the code stops compiling; delete a facade and the code still works, just worse.
Is a Facade the same as a service layer?
A service layer is usually implemented as facades, but the two ideas answer different questions. "Service layer" is architectural: it names a tier that sits between your controllers and your domain. "Facade" is a class-level pattern: one simple interface over a complicated set of collaborators. A service class that orchestrates five repositories is a facade; a service class that contains the business rules themselves is a domain service, and calling it a facade is where the god object usually starts.
Should a Facade have an interface?
The pattern doesn't need one — the subsystem has no idea the facade exists, so there is nothing to polymorphise. Add an interface when you have a concrete reason: faking the facade in a controller's tests, or swapping implementations per environment. Adding one reflexively gives you a second file that always changes with the first.
Can a Facade have more than one method?
Yes, and most do — one per task the callers actually perform. The number to watch is not the method count but the dependency count and whether the methods still describe journeys. Three methods over five subsystems is healthy. Forty methods over forty dependencies is a god object that got named after a pattern.
Does a Facade hide the subsystem completely?
No, and it shouldn't. GoF specifically notes that clients who need the subsystem directly can still use it. The facade makes the common path a single call; the unusual caller reaching past it is the pattern working as designed, not a violation. When you truly must prevent direct access — money movement, audit trails — enforce that with a transactional boundary or a permissions model, not by pretending a convenience class is a wall.
Finish the handbook, earn the certificate
All 23 Gang-of-Four patterns, each with a simulator you can click and code in seven languages. Free, no signup.