Skip to main content

Facade Pattern

Definition​

The Facade provides a unified, simplified interface to a set of interfaces in a subsystem. It defines a higher-level interface that makes the subsystem easier to use by hiding its complexity behind a clean front.

Coffee Shop Example​

Placing a coffee order involves many subsystems: checking inventory, brewing coffee, applying loyalty points, processing payment, and printing the receipt. The customer shouldn't need to know about all these steps. The CoffeeShopFacade handles the orchestration.

Structure​

Subsystem Components​

// --- Subsystem: Inventory ---
public class InventoryService
{
private readonly Dictionary<string, int> _stock = new()
{
["Espresso"] = 50,
["Latte"] = 30,
["Cappuccino"] = 25,
["Cold Brew"] = 15,
};

public bool CheckAvailability(string coffee) =>
_stock.GetValueOrDefault(coffee, 0) > 0;

public void Deduct(string coffee) =>
_stock[coffee] = _stock[coffee] - 1;
}

// --- Subsystem: Brewing ---
public record Beverage(string Name, CupSize Size, DateTime BrewedAt);

public class BrewingService
{
public Beverage Brew(string coffee, CupSize size)
{
Console.WriteLine($" Brewing {size} {coffee}...");
return new Beverage(coffee, size, DateTime.UtcNow);
}
}

// --- Subsystem: Loyalty ---
public class LoyaltyService
{
private readonly Dictionary<string, int> _points = new();

public int AddPoints(string customer, decimal amount)
{
var earned = (int)(amount * 10);
_points[customer] = _points.GetValueOrDefault(customer) + earned;
return _points[customer];
}

public int GetPoints(string customer) =>
_points.GetValueOrDefault(customer);
}

// --- Subsystem: Payment ---
public record PaymentResult(bool Success, string TransactionId);

public class PaymentService
{
public PaymentResult Charge(string customer, decimal amount)
{
Console.WriteLine($" Charging ${amount:F2} for {customer}...");
return new PaymentResult(true, $"txn_{Guid.NewGuid():N}"[..12]);
}
}

// --- Subsystem: Receipt ---
public record Order(string Customer, string Coffee, CupSize Size,
decimal Total, string TransactionId, int PointsEarned);

public class ReceiptPrinter
{
public string Print(Order order) =>
$" Receipt: {order.Size} {order.Coffee} | " +
$"${order.Total:F2} | Txn: {order.TransactionId} | " +
$"+{order.PointsEarned} pts";
}

Facade β€” Simplified Interface​

public record OrderResult(bool Success, Order Order, string Message);

public class CoffeeShopFacade
{
private readonly InventoryService _inventory;
private readonly BrewingService _brewing;
private readonly LoyaltyService _loyalty;
private readonly PaymentService _payment;
private readonly ReceiptPrinter _receipt;

private static readonly Dictionary<string, decimal> _prices = new()
{
["Espresso"] = 2.50m,
["Latte"] = 4.00m,
["Cappuccino"] = 3.75m,
["Cold Brew"] = 3.50m,
};

private static readonly Dictionary<CupSize, decimal> _sizeMarkup = new()
{
[CupSize.Small] = 0.0m,
[CupSize.Medium] = 0.50m,
[CupSize.Large] = 1.00m,
};

public CoffeeShopFacade(
InventoryService inventory,
BrewingService brewing,
LoyaltyService loyalty,
PaymentService payment,
ReceiptPrinter receipt)
{
_inventory = inventory;
_brewing = brewing;
_loyalty = loyalty;
_payment = payment;
_receipt = receipt;
}

public OrderResult PlaceOrder(string customer, string coffee, CupSize size)
{
// Step 1: Check inventory
if (!_inventory.CheckAvailability(coffee))
return new OrderResult(false, null!, $"Sorry, {coffee} is out of stock.");

// Step 2: Calculate price
var basePrice = _prices[coffee];
var total = basePrice + _sizeMarkup[size];

// Step 3: Process payment
var payment = _payment.Charge(customer, total);
if (!payment.Success)
return new OrderResult(false, null!, "Payment failed.");

// Step 4: Brew the coffee
var beverage = _brewing.Brew(coffee, size);

// Step 5: Add loyalty points
var points = _loyalty.AddPoints(customer, total);

// Step 6: Deduct inventory
_inventory.Deduct(coffee);

// Step 7: Print receipt
var order = new Order(customer, coffee, size, total, payment.TransactionId, points);
Console.WriteLine(_receipt.Print(order));

return new OrderResult(true, order, $"Here's your {size} {coffee}!");
}
}

Client β€” Dead Simple​

// The client doesn't know about inventory, brewing, loyalty, or payment subsystems
var facade = new CoffeeShopFacade(
new InventoryService(),
new BrewingService(),
new LoyaltyService(),
new PaymentService(),
new ReceiptPrinter());

var result1 = facade.PlaceOrder("Alice", "Latte", CupSize.Large);
Console.WriteLine($" β†’ {result1.Message}\n");

var result2 = facade.PlaceOrder("Bob", "Espresso", CupSize.Small);
Console.WriteLine($" β†’ {result2.Message}\n");

// Output:
// Charging $5.00 for Alice...
// Brewing Large Latte...
// Receipt: Large Latte | $5.00 | Txn: txn_a1b2c3d4e5f6 | +50 pts
// β†’ Here's your Large Latte!
//
// Charging $2.50 for Bob...
// Brewing Small Espresso...
// Receipt: Small Espresso | $2.50 | Txn: txn_g7h8i9j0k1l2 | +25 pts
// β†’ Here's your Small Espresso!
tip

The client calls one method (PlaceOrder) instead of coordinating 5 subsystems. The facade doesn't add business logic β€” it orchestrates existing subsystems.

Facade vs Mediator​

FacadeMediator
IntentSimplify a subsystem's interfaceCentralize communication between colleagues
DirectionOne-way: client β†’ facade β†’ subsystemsMulti-way: colleagues ↔ mediator ↔ colleagues
AwarenessSubsystems don't know about the facadeColleagues know about the mediator
CouplingReduces client-subsystem couplingReduces colleague-colleague coupling

.NET Real-World Usage​

  • IHostBuilder in .NET β€” simplifies host configuration behind CreateBuilder()
  • WebApplication.CreateBuilder() β€” facade over logging, configuration, DI, Kestrel
  • HttpClient β€” facade over HTTP message handlers, connection pools, and serialization
  • ORM contexts (DbContext) β€” facade over connection, transaction, change tracking, and querying

When to Use​

  • You need a simple interface to a complex subsystem
  • You want to layer your system β€” a facade for each layer
  • You need to decouple client code from subsystem implementation details

When NOT to Use​

  • The subsystem is already simple β€” a facade adds unnecessary indirection
  • You need fine-grained control over individual subsystems β€” the facade hides too much
  • The facade becomes a "god object" β€” split into multiple facades if it grows too large

Key Takeaways​

  • Facade provides a simplified, unified interface to a complex subsystem
  • It orchestrates subsystems β€” it doesn't contain business logic itself
  • Subsystems remain fully functional without the facade β€” it's optional, not mandatory
  • Multiple facades can wrap the same subsystem for different client needs

Interview Questions​

Q: Is Facade just a wrapper? Not exactly. A wrapper (like Adapter) converts one interface to another. A Facade simplifies many interfaces into one. It doesn't translate β€” it abstracts away complexity.

Q: Can a facade access subsystems directly or only through interfaces? Both work. For testability, the facade should depend on abstractions (interfaces). In practice, it may directly use subsystem classes if they're stable.

Q: What's the difference between Facade and Application Service in Clean Architecture? They're conceptually similar. An Application Service (use case) in Clean Architecture often acts as a facade β€” orchestrating domain objects and infrastructure. The pattern is the same; the naming comes from the architectural layer.