Getty Images

Tip

Using the BFF pattern to keep UIs flexible and reliable

BFF is a simple design pattern that can help developers address UI-related challenges, such as problematic coupling, overfetching of data and inconsistent error handling.

When an application only interacts with just one type of end-user interface, developers don't typically face many challenges in terms of integration between the front and back end of an application. However, it's often the case that applications need to frequently accommodate new types of users, particularly mobile users running on a variety of different devices, access levels and software types. This can be a huge challenge for developers, especially if they don't have the time, resources or flexibility to regularly overhaul the UI.

There are all sorts of reasons for this UI conundrum, but many of the difficulties stem from tight coupling between back-end application services and front-end UI code. The Backends-for-Frontends (BFF) pattern can help address these challenges by insulating an application's front-end interface from the effects of changes made to back-end domain services, but developers must take certain steps to implement it successfully.

Why use the Backends-for-Frontends pattern?

The BFF pattern essentially creates a dedicated back-end abstraction for each user-facing service employed by the UI. That abstracted component performs two important duties:

  1. It acts as a translator component that can automatically transform data into the format the client needs.
  2. It provides front-end UI components the data needs to continue regular operations, even when the actual back-end services are unavailable.

The BFF pattern can also help when developers want to use shared sets of generic, reusable APIs for basic application processes. While reusable APIs eliminate manual code maintenance tasks, they potentially pose two harmful side effects: overfetching of data and dangerously high request rates.

To illustrate this, picture a web application that acts as an order processing system for an e-commerce website that sells computers. The application includes a class model composed of the following fields:

  • Id -- the unique identifier for each product;
  • Name -- the name of the product, e.g., Dell Inspiron, Lenovo ThinkPad, etc.;
  • Type -- the type of product, e.g., laptop, TV, mobile, etc.;
  • Price -- the price of the product; and
  • Discount -- any special sales or discounts that affect the price of the product.

Here's what the code for that class, which we call Product, might look like in a C#-based application:

public class Product
{
 
     public int Id { get; set; }
 
     public string Name { get; set; }
 
     public string Type { get; set; }
 
     public decimal Price ( get; set; )
 
public decimal Discount { get; set; }
 
}

Suppose someone wants to view the names of all products that cost less than $2,000. When the target service receives the request for this data, it may only need to fetch from one or two of the fields in the Product class to fill the request. However, because of its structure, this class still forces the service to pull data for each field it contains -- a classic example of overfetching. The constant aggregation of unnecessary data that results from overfetching can put a lot of stress on the back end.

Next, imagine that the application uses three other class models in addition to the Product class: Cart, Customer and Order. A client makes a request for the UI to display all the details associated with a particular customer's order history. First, the target service needs to fetch a customer record. Then, it has to fetch all the specific info tied to each of the customer's individual orders. This type of scenario likely involves a lot of data requests; if left unchecked, this introduces high levels of code complexity and drowns the back end in performance-dampening workload levels.

Other benefits of the BFF pattern

There are a few other benefits to the BFF pattern that add to its appeal as a strategic architectural pattern, including the following:

  • Improved security. Developers can use the BFF pattern to identify and hide sensitive information before responding to a client's request. The added abstraction also imposes a barrier that makes breaches difficult for intruders.
  • Shared team ownership. Since the front-end and back-end applications are decoupled, several teams can work on different parts of the application simultaneously, without waiting for other teams to complete their tasks.
  • Communication efficiency. The BFF layer creates an effective request aggregator and can act as a facade for downstream services to reduce the chattiness of client applications.
  • Consistent error handling. The BFF pattern helps translate error codes from downstream services and, in turn, identify ways to enforce consistent error handling.
  • Reduced waiting times. Instead of waiting for the back-end team to finish working on a particular API, the client development team can comfortably sit at the top of the delivery pipeline and take autonomous control over front-end projects.
The BFF pattern creates different back-end services for multiple front-end applications or interfaces.

How the BFF pattern works

The BFF pattern creates different back-end services for multiple front-end applications or interfaces. Use this pattern in the following situations:

  • The application relies on shared, general-purpose back-end services.
  • Application services consume data sets that require intensive aggregation on the back end.
  • Developers want to add customizations to general-purpose back-end code and then use that customized code for multiple UIs.
  • Specific client interfaces require developers to optimize or modify back-end code.

Figure 1 illustrates a typical BFF pattern implementation.

A basic diagram of the Backends-for-Frontends (BFF) pattern
Figure 1. BFF pattern diagram

Using the components found in Figure 1, Figure 2 shows what the solution structure for a BFF would typically look like in an editor.

An example solution pattern for the Backends-for-Frontends (BFF) pattern
Figure 2. BFF application solution pattern

During the application's operation, these BFF services only call the specific back-end domain services that are needed to complete the request. Here's an example of what the code structure of these methods would look like in the back end of a service-based application written in C#:

[HttpGet("GetCartItems")]


public List<cart> GetCartItems()
{
     return dataContext.Carts.Where(
           cart => cart.IsValid == true).ToList();
}
 
[HttpPost("SaveCart")]
 
public void SaveCart([FromBody] Cart cart)
{
     dataContext.Carts.Add(cart);
     dataContext.Save();
}

Tips to implement a BFF pattern

While the BFF pattern is relatively simple, there are still some basic things developers must be mindful of in order to use it effectively. Here are four techniques that can help keep the implementation of a BFF pattern on track:

  1. Use generic domain services and specific application services. While the BFF pattern does encourage customized APIs for specific clients and UIs, the domain services should always be reusable, generic components.
  2. Deploy the BFF pattern and respective clients together. Since the BFF layer and the UI are tightly coupled, treat these two elements as a single deployment unit. In short, if you change the BFF, you should always update the UI as well -- and vice versa.
  3. Involve the UI team. The team that manages the UI should have access to documentation that clearly explains the specifics of BFF. This makes it simpler to create APIs that satisfy unique UI requirements. Down the line, this also streamlines server and client component deployment.
  4. Ensure the BFF layer only encompasses presentation logic. Typically, an application contains the following levels of logic:
    • presentation logic, which translates back-end data into the correct UI format;
    • flow or composition logic, which defines how application components communicate;
    • domain logic, which represents the application's underlying business logic; and
    • persistence logic, which determines when and how the application should store data internally.
    The BFF pattern should only cover the presentation logic and should not handle any logic unrelated to the UI. The flow logic of an application is typically a domain-specific logic; if you allow the BFF layer to handle flow logic, you risk creating a lot of code redundancy.

Dig Deeper on Application development and design