Implementing the BFF Pattern with Microservices: Product and Order services
In microservice architectures, different services handle specific domains, such as product information, orders, and user accounts. While this architecture is highly scalable and maintainable, it can lead to challenges when building user interfaces. Different frontend applications (web, mobile, etc.) often need tailored data from multiple services, resulting in complex interactions between frontend applications and backends. This is where the Backend for Frontend (BFF) pattern becomes valuable.
In this blog, we will explore the BFF pattern in microservices using Product and Order services as an example. We’ll walk through how the BFF pattern works, its advantages, and an example implementation with .NET Core as the backend and React as the frontend.
Embark on a journey of continuous learning and exploration with DotNet-FullStack-Dev. Uncover more by visiting our https://dotnet-fullstack-dev.blogspot.com reach out for further information.
What is the BFF Pattern?
The Backend for Frontend (BFF) pattern is an architectural pattern in which a separate backend service is created for each user interface or client application. Instead of having each frontend (web, mobile, etc.) communicate directly with multiple microservices, the BFF acts as an intermediary between the frontend and backend services.
Key Characteristics of the BFF Pattern:
- Client-Specific Backends: Different frontends (e.g., web, mobile) have different needs, and the BFF pattern allows each frontend to have its own backend service.
- Simplified Frontend Logic: The frontend interacts with a single backend, which simplifies the UI logic.
- Aggregated Responses: The BFF can aggregate data from multiple microservices, tailoring the response specifically to the needs of the frontend.
Benefits of the BFF Pattern:
- Separation of Concerns: Each frontend has its own backend, tailored to its specific needs.
- Reduced Complexity: Frontends don’t need to handle complex logic for aggregating data from multiple services.
- Optimized Performance: The BFF can batch requests, cache data, and reduce network overhead.
- Decoupled UIs: Web, mobile, and other UIs are decoupled from the backend services and only interact with their specific BFF.
Microservices Example: Product and Order Services
Let’s assume we have two microservices:
- Product Service: Responsible for managing product data (e.g., listing products, product details).
- Order Service: Responsible for handling orders (e.g., creating orders, retrieving order history).
Use Case:
We are building a web application and a mobile app. Both need to interact with these services, but their requirements differ:
- Web App: Requires detailed product information and order history.
- Mobile App: Requires only brief product details (e.g., name and price) and the ability to place orders quickly.
Instead of having both apps communicate directly with Product and Order services, we can introduce a BFF layer to handle the communication. Each app will have its own BFF that aggregates data from the Product and Order services and provides a simplified API for the frontend.
BFF Architecture with Product and Order Microservices
Architecture Overview:
- Microservices Layer: The Product and Order services are standalone, providing APIs for product and order management.
- BFF Layer: Two separate BFFs are introduced — one for the web application and one for the mobile app. Each BFF communicates with both Product and Order services.
- Frontend Layer: The web and mobile apps only interact with their respective BFFs, not the underlying microservices.
1. Microservices Layer
We have two microservices:
- Product Service: Provides APIs like
GET /products
andGET /products/{id}
for product details. - Order Service: Provides APIs like
POST /orders
for creating an order andGET /orders
for retrieving order history.
2. Web BFF and Mobile BFF
- Web BFF: Tailored to the web application, it aggregates data from the Product and Order services and returns detailed product information along with order history.
- Mobile BFF: Tailored to the mobile app, it focuses on providing concise product data and quick access to place orders.
Example Implementation of BFF Pattern
Let’s walk through how to implement the BFF pattern using .NET Core for the backend and React for the frontend.
Step 1: Microservices (Product and Order)
Product Service
The Product Service exposes APIs to retrieve product information. Here’s an example of a simple product API in .NET Core:
[ApiController]
[Route("api/products")]
public class ProductController : ControllerBase
{
private readonly IProductService _productService;
public ProductController(IProductService productService)
{
_productService = productService;
}
[HttpGet]
public async Task<IActionResult> GetProducts()
{
var products = await _productService.GetAllProductsAsync();
return Ok(products);
}
[HttpGet("{id}")]
public async Task<IActionResult> GetProductById(int id)
{
var product = await _productService.GetProductByIdAsync(id);
if (product == null)
return NotFound();
return Ok(product);
}
}
Order Service
The Order Service handles order creation and retrieval:
[ApiController]
[Route("api/orders")]
public class OrderController : ControllerBase
{
private readonly IOrderService _orderService;
public OrderController(IOrderService orderService)
{
_orderService = orderService;
}
[HttpPost]
public async Task<IActionResult> CreateOrder([FromBody] OrderRequest orderRequest)
{
var order = await _orderService.CreateOrderAsync(orderRequest);
return CreatedAtAction(nameof(GetOrderById), new { id = order.Id }, order);
}
[HttpGet("{id}")]
public async Task<IActionResult> GetOrderById(int id)
{
var order = await _orderService.GetOrderByIdAsync(id);
if (order == null)
return NotFound();
return Ok(order);
}
}
Step 2: Web BFF and Mobile BFF
Web BFF
The Web BFF is responsible for aggregating detailed product and order data. Here’s an example:
[ApiController]
[Route("api/web-bff")]
public class WebBffController : ControllerBase
{
private readonly IProductService _productService;
private readonly IOrderService _orderService;
public WebBffController(IProductService productService, IOrderService orderService)
{
_productService = productService;
_orderService = orderService;
}
[HttpGet("dashboard")]
public async Task<IActionResult> GetDashboardData()
{
var products = await _productService.GetAllProductsAsync();
var orders = await _orderService.GetOrderHistoryAsync();
var dashboardData = new
{
Products = products,
OrderHistory = orders
};
return Ok(dashboardData);
}
}
Mobile BFF
The Mobile BFF provides lightweight data, focusing on quick product lookup and order placement:
[ApiController]
[Route("api/mobile-bff")]
public class MobileBffController : ControllerBase
{
private readonly IProductService _productService;
private readonly IOrderService _orderService;
public MobileBffController(IProductService productService, IOrderService orderService)
{
_productService = productService;
_orderService = orderService;
}
[HttpGet("products")]
public async Task<IActionResult> GetProducts()
{
var products = await _productService.GetProductSummariesAsync(); // returns only name, price, etc.
return Ok(products);
}
[HttpPost("order")]
public async Task<IActionResult> PlaceOrder([FromBody] OrderRequest orderRequest)
{
var order = await _orderService.CreateOrderAsync(orderRequest);
return CreatedAtAction(nameof(GetOrderById), new { id = order.Id }, order);
}
}
Step 3: Frontend Integration
Web Application (React)
The web application communicates with the Web BFF to retrieve the dashboard data:
useEffect(() => {
fetch('/api/web-bff/dashboard')
.then(response => response.json())
.then(data => {
setProducts(data.products);
setOrderHistory(data.orderHistory);
});
}, []);re
Mobile Application (React Native)
The mobile app interacts with the Mobile BFF to fetch products and place orders:
useEffect(() => {
fetch('/api/mobile-bff/products')
.then(response => response.json())
.then(data => setProducts(data));
}, []);
const placeOrder = (orderRequest) => {
fetch('/api/mobile-bff/order', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(orderRequest),
});
};
Advantages of Using the BFF Pattern
- Optimized Data: The BFF can transform and aggregate data from multiple microservices, tailoring responses specifically to the needs of each client.
- Reduced Network Overhead: The frontend only communicates with one backend (the BFF) rather than multiple microservices.
- Cleaner Frontend Code: The frontend doesn’t need to handle complex logic for fetching data from multiple microservices or deal with different API versions. This results in cleaner, more maintainable frontend code.
- Flexibility: Each BFF can evolve independently according to the needs of the specific frontend. For example, if the mobile app requires a new feature that the web app doesn’t, the Mobile BFF can be updated without affecting the Web BFF.
- Security: The BFF can enforce security policies and handle sensitive data, such as user authentication or authorization, before passing relevant data to the frontend.
- Improved Performance: The BFF can implement optimizations like caching or rate-limiting requests to reduce the load on microservices and improve overall application performance.
Conclusion
The Backend for Frontend (BFF) pattern simplifies the communication between frontends and microservices in modern distributed systems. By creating separate backends tailored for each frontend, you can reduce complexity, improve performance, and create more maintainable systems. In our example, we saw how the BFF pattern can be implemented with Product and Order services to cater to the distinct needs of a web application and a mobile app.
By using a BFF layer, you ensure that each client gets exactly what it needs from the microservices while keeping the frontend code clean and focused on the user experience. The BFF pattern also offers a natural way to handle differences between various client types, making it a valuable strategy for building modern, scalable, and maintainable microservice-based applications.