IConfiguration vs IOptions NET
Synchronous and Asynchronous in .NET Core
Model Binding and Validation in ASP.NET Core
ControllerBase vs Controller in ASP.NET Core
ConfigureServices and Configure methods
IHostedService interface in .NET Core
ASP.NET Core request processing
| API Gateway | Service-Discovery-and-health-Check | |
The Backend for Frontend (BFF) Pattern |
The Backend for Frontend (BFF) pattern is an architectural design where a dedicated backend service is created for each type of frontend client, such as a web application, mobile app, or third-party service. This contrasts with a single, general-purpose API that serves all clients, leading to a tailored and optimized experience for each. The BFF service acts as a mediator, aggregating, transforming, and filtering data from underlying microservices to meet the precise needs of its specific frontend.
The BFF pattern introduces a dedicated backend layer for each frontend type. Instead of having a single generic API for all clients, each frontend gets its own backend that:
Think of it as a personal assistant for each frontendβfiltering, formatting, and simplifying the backend complexity.
Imagine you have:
Instead of forcing both to use the same API, you create:
Each BFF communicates with the same set of microservices but tailors the experience for its client.
Imagine an e-commerce platform with two main clients: a web browser application and a mobile app. Both need to display a user's profile and recent order history. However, they have different data and performance requirements:
The Web App has more screen space and needs detailed user information and a comprehensive list of orders with full item details.
The Mobile App requires a lightweight, fast-loading interface with only essential user information and a limited summary of recent orders.
A single, generic backend API would either have to over-fetch data for the mobile client (sending more data than needed) or require complex filtering logic on the client side, leading to inefficiency and slower performance.
Instead, we can implement the BFF pattern by creating a dedicated .NET Core backend for each client.
First, let's assume we have two core microservices that provide generic data.
User Service (Controllers/UserController.cs)
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult GetUser(string id)
{
// Imagine fetching from a database
return Ok(new {
Id = id,
FirstName = "Jane",
LastName = "Doe",
Email = "jane.doe@example.com",
Phone = "555-1234",
FullAddress = "123 Main St, Anytown, USA"
});
}
}
Order Service (Controllers/OrderController.cs)
[ApiController]
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
[HttpGet("user/{userId}")]
public IActionResult GetOrders(string userId)
{
// Return a full list of orders for a user
return Ok(new[] {
new { OrderId = 1, OrderDate = DateTime.Now, Total = 59.99, ItemCount = 2, Items = new[] { /* ...detailed items... */ } },
new { OrderId = 2, OrderDate = DateTime.Now.AddDays(-1), Total = 12.50, ItemCount = 1, Items = new[] { /* ...detailed items... */ } }
});
}
}
Next, we create a separate ASP.NET Core API project for each frontend. These are the BFFs. They will use HttpClient to communicate with the core microservices.
Web App BFF Project (Controllers/WebBffController.cs)
This BFF aggregates data for the desktop web UI.
[ApiController]
[Route("api/[controller]")]
public class WebBffController : ControllerBase
{
private readonly HttpClient _httpClient;
public WebBffController(HttpClient httpClient)
{
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri("http://localhost:5001/"); // Base URL for core microservices
}
[HttpGet("user-details/{userId}")]
public async Task<IActionResult> GetUserDetailsForWeb(string userId)
{
// Aggregate calls to both microservices
var userTask = _httpClient.GetFromJsonAsync<object>($"/api/user/{userId}");
var ordersTask = _httpClient.GetFromJsonAsync<object[]>($"/api/order/user/{userId}");
await Task.WhenAll(userTask, ordersTask);
var response = new {
User = userTask.Result,
Orders = ordersTask.Result
};
return Ok(response);
}
}
Mobile App BFF Project (Controllers/MobileBffController.cs)
This BFF aggregates and transforms data to be lightweight for the mobile UI.
[ApiController]
[Route("api/[controller]")]
public class MobileBffController : ControllerBase
{
private readonly HttpClient _httpClient;
public MobileBffController(HttpClient httpClient)
{
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri("http://localhost:5001/"); // Base URL for core microservices
}
[HttpGet("user-details/{userId}")]
public async Task<IActionResult> GetUserDetailsForMobile(string userId)
{
// Aggregate calls to microservices and transform the data
var user = await _httpClient.GetFromJsonAsync<JObject>($"/api/user/{userId}");
var orders = await _httpClient.GetFromJsonAsync<JArray>($"/api/order/user/{userId}");
// Create a lightweight response for mobile
var mobileResponse = new {
FirstName = user["firstName"],
RecentOrders = orders.Select(o => new {
OrderId = o["orderId"],
Total = o["total"]
}).Take(3) // Only show the three most recent orders
};
return Ok(mobileResponse);
}
}
A request from the web app goes to WebBffController. This controller calls the core User and Order services, aggregates the full data, and returns it.
A request from the mobile app goes to MobileBffController. This controller calls the same core services but then filters the user data and limits the order history before sending a lightweight response, preventing over-fetching.
The core microservices remain generic and unaware of the different clients. Any changes to the UI-specific requirements only affect the corresponding BFF, not the core services or other BFFs.
IHttpClientFactory with Polly for resilience. | API Gateway | Service-Discovery-and-health-Check | |