Manikandan โ€” Manikandan
Updated on 5 min read Manikandan Design Patterns

Facade Design Pattern in .NET Core API

A practical and beginner-friendly guide to the Facade pattern in C#, with simple and layered facade variations for clean API orchestration.

Intro

Facade is a structural design pattern that provides a simple interface over a set of complex subsystems.

It is useful when the client should not deal with many low-level services directly.

In API projects, Facade helps keep controllers thin by moving orchestration into one focused entry point.

What Is the Facade Pattern?

The Facade pattern hides subsystem complexity behind a single, unified interface.

In simple terms:

  • The client calls one high-level method.
  • The facade coordinates multiple subsystem calls.
  • The client does not need to know subsystem ordering or internal details.

This reduces coupling and makes calling code easier to read.

Facade Class Example

Suppose we have a checkout flow that needs inventory, payment, and shipping services.

public interface IInventoryService
{
bool Reserve(string sku, int quantity);
}
public interface IPaymentService
{
bool Charge(string customerId, decimal amount);
}
public interface IShippingService
{
string CreateShipment(string customerId, string sku, int quantity);
}
public sealed class CheckoutFacade
{
private readonly IInventoryService _inventory;
private readonly IPaymentService _payment;
private readonly IShippingService _shipping;
public CheckoutFacade(
IInventoryService inventory,
IPaymentService payment,
IShippingService shipping)
{
_inventory = inventory;
_payment = payment;
_shipping = shipping;
}
public string PlaceOrder(string customerId, string sku, int quantity, decimal amount)
{
if (!_inventory.Reserve(sku, quantity))
{
return "Inventory unavailable";
}
if (!_payment.Charge(customerId, amount))
{
return "Payment failed";
}
return _shipping.CreateShipment(customerId, sku, quantity);
}
}

Usage

var result = checkoutFacade.PlaceOrder("CUST-100", "SKU-20", 2, 1499.00m);
Console.WriteLine(result);

Why This Works

  • Client code depends on one facade instead of many subsystems.
  • Subsystem interaction order is centralized in one place.
  • Changes to subsystem workflow do not affect every caller.
  • Testing becomes easier because orchestration is isolated.

When to Use It

  • When clients repeatedly call multiple services in a fixed sequence.
  • When subsystem APIs are too detailed for most callers.
  • When you want a stable, business-focused entry point.
  • When controllers/services are growing with orchestration logic.

If your use case is only a direct one-to-one service call, a facade might be unnecessary.

Important Note for ASP.NET Core

In ASP.NET Core, facades fit well as application services registered in DI.

builder.Services.AddScoped<IInventoryService, InventoryService>();
builder.Services.AddScoped<IPaymentService, PaymentService>();
builder.Services.AddScoped<IShippingService, ShippingService>();
builder.Services.AddScoped<CheckoutFacade>();

Controllers can depend on CheckoutFacade and stay focused on HTTP concerns.

Core Components of the Pattern

PartPurposeExample in this article
FacadeSimplified entry point to subsystem operationsCheckoutFacade
SubsystemsLow-level services that perform actual workIInventoryService, IPaymentService, IShippingService
ClientUses the high-level entry pointAPI controller or application service

Common Facade Variations

01-SimpleFacade

A single facade class wraps a set of subsystem calls for one use case.

It is best when workflow is straightforward and the same orchestration is used by many callers.

public sealed class NotificationFacade
{
private readonly ITemplateService _template;
private readonly IEmailSender _email;
public NotificationFacade(ITemplateService template, IEmailSender email)
{
_template = template;
_email = email;
}
public void SendWelcome(string userEmail, string userName)
{
var body = _template.Render("Welcome", userName);
_email.Send(userEmail, "Welcome", body);
}
}

02-LayeredFacade

Multiple facades are organized in layers, where a top-level facade coordinates lower-level facades.

This is useful when workflows are large and need separation by domain boundaries.

public sealed class BillingFacade
{
public bool ProcessInvoice(string customerId, decimal amount) => true;
}
public sealed class FulfillmentFacade
{
public string ProcessShipment(string customerId, string sku, int quantity) => "SHIP-1001";
}
public sealed class OrderApplicationFacade
{
private readonly BillingFacade _billing;
private readonly FulfillmentFacade _fulfillment;
public OrderApplicationFacade(BillingFacade billing, FulfillmentFacade fulfillment)
{
_billing = billing;
_fulfillment = fulfillment;
}
public string CompleteOrder(string customerId, string sku, int quantity, decimal amount)
{
if (!_billing.ProcessInvoice(customerId, amount))
{
return "Billing failed";
}
return _fulfillment.ProcessShipment(customerId, sku, quantity);
}
}

How the Variations Differ

VariationMain ideaBest use caseTrade-off
SimpleFacadeOne facade over a small subsystem groupCommon, fixed workflowsCan become large as features grow
LayeredFacadeTop-level facade over multiple facadesComplex domains with bounded responsibilitiesMore classes and composition setup

SOLID Principles Behind It

SRP โ€” Single Responsibility Principle

The facade focuses on orchestration, while each subsystem keeps its own specialized logic.

OCP โ€” Open/Closed Principle

You can extend behavior by adding new subsystem services or a new facade layer without changing every client.

DIP โ€” Dependency Inversion Principle

Facade depends on subsystem abstractions (IInventoryService, IPaymentService), not concrete implementations.

ISP โ€” Interface Segregation Principle

Clients can depend on a small, scenario-specific facade API instead of many subsystem contracts.

Advantages and Disadvantages

Advantages

  • Simplifies usage of complex subsystems.
  • Reduces coupling between clients and low-level services.
  • Keeps orchestration logic centralized and maintainable.
  • Makes controllers and handlers cleaner.

Disadvantages

  • Facade can become a god object if too many responsibilities are added.
  • May hide useful subsystem capabilities from advanced callers.
  • Extra abstraction can be unnecessary for very small workflows.

UML Diagram

classDiagram
class Client
class CheckoutFacade {
+PlaceOrder(customerId, sku, quantity, amount) string
}
class IInventoryService {
<<interface>>
+Reserve(sku, quantity) bool
}
class IPaymentService {
<<interface>>
+Charge(customerId, amount) bool
}
class IShippingService {
<<interface>>
+CreateShipment(customerId, sku, quantity) string
}
Client --> CheckoutFacade
CheckoutFacade --> IInventoryService
CheckoutFacade --> IPaymentService
CheckoutFacade --> IShippingService

A Quick Usage Example

public sealed class OrdersController
{
private readonly CheckoutFacade _checkout;
public OrdersController(CheckoutFacade checkout)
{
_checkout = checkout;
}
public string Post()
{
return _checkout.PlaceOrder("CUST-500", "SKU-81", 1, 599.00m);
}
}

The controller uses one facade call instead of coordinating inventory, payment, and shipping directly.

How to Validate the Pattern

If you want to validate a Facade implementation, check these points:

  • the client should call only the facade for the target workflow
  • subsystem calls should happen in the expected order
  • failure paths should be translated into clear facade-level outcomes
  • replacing subsystem implementations should not require client changes

A small xUnit example:

[Fact]
public void CheckoutFacade_Should_Return_PaymentFailed_When_Charge_Fails()
{
var inventory = new FakeInventoryService(success: true);
var payment = new FakePaymentService(success: false);
var shipping = new FakeShippingService();
var facade = new CheckoutFacade(inventory, payment, shipping);
var result = facade.PlaceOrder("C-1", "SKU-1", 1, 100m);
Assert.Equal("Payment failed", result);
}
Share:
Back to Blog