beginner By Mathias Paulenko

Facade Pattern

Provide a simplified interface to a complex subsystem. A structural pattern that hides implementation details behind a clean API.

Topics: design

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

VariantUse Case
Class FacadeStatic methods for stateless operations
Service FacadeInjected dependency that wraps repositories and external APIs
API GatewayHTTP-level facade that aggregates multiple microservices
Module FacadePublic 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.

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.