Facade Pattern
Provide a simplified interface to a complex subsystem. A structural pattern that hides implementation details behind a clean API.
Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.
Overview
The Facade Pattern provides a simplified, unified interface to a complex subsystem. Instead of forcing clients to interact with dozens of interdependent classes, a facade exposes only the operations they need. This reduces coupling, improves readability, and makes the subsystem easier to evolve.
Consider a video conversion library. Without a facade, clients must manually configure codecs, bit rate calculators, file splitters, and audio mixers. With a facade, they call convert("movie.mp4", "output.avi") and the facade orchestrates everything internally.
When to Use
Use the Facade Pattern when:
- A subsystem is complex and has many interdependent components
- You want to provide a simple entry point for common operations
- You need to decouple client code from subsystem implementation details
- Multiple subsystems must be coordinated for a single task
- You want to layer your architecture (e.g., service layer over repositories)
When to Avoid
- The subsystem is already simple; adding a facade is unnecessary indirection
- Every client needs low-level control; a facade would hide too much
- You are trying to fix a badly designed subsystem instead of refactoring it
Solution
Python
class VideoDecoder:
def decode(self, file):
return f"Decoded {file}"
class VideoEncoder:
def encode(self, stream, format):
return f"Encoded to {format}"
class BitRateCalculator:
def calculate(self, source):
return 1024
class AudioMixer:
def mix(self, stream):
return f"Mixed audio for {stream}"
class VideoConverter:
"""Facade that hides the complexity of video conversion."""
def __init__(self):
self._decoder = VideoDecoder()
self._encoder = VideoEncoder()
self._bitrate = BitRateCalculator()
self._audio = AudioMixer()
def convert(self, source_file, destination_format):
decoded = self._decoder.decode(source_file)
bitrate = self._bitrate.calculate(decoded)
mixed = self._audio.mix(decoded)
return self._encoder.encode(mixed, destination_format)
# Client code
converter = VideoConverter()
result = converter.convert("movie.mp4", "avi")
print(result)
Java
class VideoDecoder {
String decode(String file) { return "Decoded " + file; }
}
class VideoEncoder {
String encode(String stream, String format) {
return "Encoded to " + format;
}
}
class BitRateCalculator {
int calculate(String source) { return 1024; }
}
class AudioMixer {
String mix(String stream) { return "Mixed " + stream; }
}
class VideoConverter {
private final VideoDecoder decoder = new VideoDecoder();
private final VideoEncoder encoder = new VideoEncoder();
private final BitRateCalculator bitrate = new BitRateCalculator();
private final AudioMixer audio = new AudioMixer();
public String convert(String sourceFile, String destinationFormat) {
String decoded = decoder.decode(sourceFile);
int rate = bitrate.calculate(decoded);
String mixed = audio.mix(decoded);
return encoder.encode(mixed, destinationFormat);
}
}
// Client code
VideoConverter converter = new VideoConverter();
System.out.println(converter.convert("movie.mp4", "avi"));
JavaScript
class VideoDecoder {
decode(file) { return `Decoded ${file}`; }
}
class VideoEncoder {
encode(stream, format) { return `Encoded to ${format}`; }
}
class BitRateCalculator {
calculate(source) { return 1024; }
}
class AudioMixer {
mix(stream) { return `Mixed ${stream}`; }
}
class VideoConverter {
constructor() {
this.decoder = new VideoDecoder();
this.encoder = new VideoEncoder();
this.bitrate = new BitRateCalculator();
this.audio = new AudioMixer();
}
convert(sourceFile, destinationFormat) {
const decoded = this.decoder.decode(sourceFile);
const rate = this.bitrate.calculate(decoded);
const mixed = this.audio.mix(decoded);
return this.encoder.encode(mixed, destinationFormat);
}
}
// Client code
const converter = new VideoConverter();
console.log(converter.convert('movie.mp4', 'avi'));
Explanation
The Facade Pattern has three participants:
- Facade (
VideoConverter): The simplified interface clients interact with - Subsystem classes (
VideoDecoder,VideoEncoder, etc.): The complex components the facade coordinates - Client: Code that uses the facade instead of subsystem classes directly
The facade does not add new functionality; it composes existing subsystem operations into higher-level workflows.
Variants
| Variant | Use Case |
|---|---|
| Class Facade | Static methods for stateless operations |
| Service Facade | Injected dependency that wraps repositories and external APIs |
| API Gateway | HTTP-level facade that aggregates multiple microservices |
| Module Facade | Public module API that hides internal file structure |
What Works
- Keep the facade thin. It should orchestrate, not implement. Business logic belongs in the subsystem or a separate service layer.
- Allow direct subsystem access. Advanced clients should still be able to bypass the facade for fine-grained control.
- Use dependency injection. Inject subsystem components into the facade for testability instead of hardcoding constructors.
- Document what the facade hides. A facade that silently retries failed HTTP requests should document this behavior to avoid surprising clients.
- One facade per subsystem. Do not create a single mega-facade for unrelated systems; it becomes a God Object.
Common Mistakes
- Putting business logic in the facade turns it into an unmaintainable middle layer. Facades delegate; they do not decide.
- Hiding too much forces every client to request facade changes for specialized needs. Expose a second “advanced” interface if needed.
- Creating a facade for a trivial subsystem adds indirection without value. A facade over three simple classes is unnecessary.
- Not updating the facade when the subsystem changes causes the facade to become a broken abstraction.
- Multiple facades with overlapping responsibilities confuse clients about which to use. Consolidate or clearly separate concerns.
Real-World Examples
ORM Session
SQLAlchemy’s Session is a facade over connection pools, transaction management, and unit-of-work tracking. Developers call session.commit() without managing individual inserts and updates.
Framework Router
Express.js app.get('/users', handler) is a facade over HTTP parsing, middleware chains, request routing, and response serialization.
Cloud SDK
AWS S3 upload_file(bucket, key, path) hides multipart uploads, retry logic, checksum validation, and credential refresh behind one method call.
Advanced Topics
Scenario: Facade for Checkout Subsystem
// Facade: simplify a complex subsystem
// Subsystem: 5 services the client should not know about
class InventoryService {
async checkStock(itemId: string): Promise<boolean> { /* ... */ return true; }
async reserve(itemId: string, qty: number): Promise<void> { /* ... */ }
}
class PricingService {
calculateTotal(items: CartItem[]): number {
return items.reduce((sum, i) => sum + i.price * i.qty, 0);
}
applyCoupon(total: number, coupon: string): number { /* ... */ return total * 0.9; }
}
class PaymentService {
async charge(amount: number, token: string): Promise<PaymentResult> { /* ... */ return { success: true, txId: "tx_123" }; }
}
class ShippingService {
async createLabel(address: Address): Promise<string> { /* ... */ return "TRK_456"; }
calculateCost(address: Address): number { /* ... */ return 9.99; }
}
class NotificationService {
async sendConfirmation(email: string, orderId: string): Promise<void> { /* ... */ }
}
// Facade: one simple interface for the client
class CheckoutFacade {
constructor(
private inventory: InventoryService,
private pricing: PricingService,
private payment: PaymentService,
private shipping: ShippingService,
private notification: NotificationService
) {}
async checkout(cart: CartItem[], coupon: string, paymentToken: string, address: Address): Promise<OrderResult> {
// 1. Check stock and reserve
for (const item of cart) {
const inStock = await this.inventory.checkStock(item.id);
if (!inStock) throw new Error(`Item ${item.id} out of stock`);
await this.inventory.reserve(item.id, item.qty);
}
// 2. Calculate price
let total = this.pricing.calculateTotal(cart);
if (coupon) total = this.pricing.applyCoupon(total, coupon);
total += this.shipping.calculateCost(address);
// 3. Charge
const paymentResult = await this.payment.charge(total, paymentToken);
if (!paymentResult.success) throw new Error("Payment failed");
// 4. Create shipment
const trackingNumber = await this.shipping.createLabel(address);
// 5. Notify
await this.notification.sendConfirmation(address.email, paymentResult.txId);
return { orderId: paymentResult.txId, total, trackingNumber };
}
}
// Usage: client calls one method
const facade = new CheckoutFacade(
new InventoryService(),
new PricingService(),
new PaymentService(),
new ShippingService(),
new NotificationService()
);
const result = await facade.checkout(cart, "SAVE10", paymentToken, address);
console.log(result); // { orderId, total, trackingNumber }
Lessons:
- Facade simplifies a complex subsystem into a simple interface
- The client does not know about the 5 internal services
- The facade orchestrates: stock -> price -> payment -> shipping -> notify
- It does not hide services: the client can use them directly if needed
- Facade vs Mediator: Facade simplifies access; Mediator centralizes communication
### Facade vs API Gateway: which do I use?
Facade is a code pattern: simplifies an internal subsystem in a class. API Gateway is an infrastructure pattern: a proxy between external clients and microservices. Facade lives in code; API Gateway lives in the network. Use Facade to simplify calls between internal modules. Use API Gateway to unify external APIs, add auth, rate limiting and routing. Both simplify: Facade at class level, API Gateway at network level. Related Resources
Adapter Pattern
Convert the interface of a class into another interface clients expect. A structural design pattern for interface compatibility.
PatternProxy Pattern
Provide a surrogate or placeholder for another object to control access to it. A structural design pattern for access control, lazy loading, and logging.
PatternMediator Pattern
Define an object that encapsulates how a set of objects interact. A behavioral design pattern for reducing chaotic dependencies.
Frequently Asked Questions
- What is the difference between Facade and Adapter?
- [Adapter](/patterns/adapter-pattern/) changes an interface to match what a client expects. Facade simplifies a complex interface without changing its contracts.
- Can a facade expose subsystem methods directly?
- Yes, this is called an "optional facade." Clients can use the simple facade methods or access subsystem classes for advanced use cases.
- Is a REST API Gateway a Facade?
- Yes. An API Gateway is a facade at the network layer, aggregating calls to multiple microservices into a single client-facing endpoint.