The Power of BFF in the Microservices World

Orçun Yılmaz
Stackademic
Published in
6 min readNov 5, 2023

--

There is no new day in the world of software development that does not come with new challenges. Software development is evolving like no other.

In the earlier days of our lives (Not Joey’s show, obviously), there was only one client, like a web or desktop app, that needed data to be fetched with necessary info in it.

But now, we have so many clients like Android, iOS, smart televisions, desktop apps, tablets, etc. When you consider the world of microservices, there is a sort of Shibuya cross in every one of our services.

When you consider this situation as power, the words come into Uncle Ben’s mouth as always;

Great power comes with great responsibility.

What is the responsibility here?

Well, it is fetching your data to the clients, as fast as you can, as evolvable as you can, and as securely as you can.

How to do it?

Let’s discuss it.

Have you ever heard of Backend-for-Frontend?

What is Backend-for-Frontend (BFF)?

The BFF pattern is a great solution for the challenges that arise because of modern application development. At its core, BFF offers a dedicated layer that acts as a middleware between front-end clients and back-end services.

You may think of every BFF layer as a valet for your needs, where each layer is responsible for aggregating, filtering, and tailoring data to the specific requirements of each frontend client.

Example Use-case

To truly understand and use the power of BFF, here is an example use case in the context of an e-commerce platform. Let’s say we have a homepage that needs to display various data, like product listings, news updates, user profiles, etc. Each client type, for example, a mobile app, a desktop application, or any other device, demands specific data in a format that suits its needs.

@RestController
public class BffController {

@Autowired
private ProductService productService;

@Autowired
private NewsService newsService;

@Autowired
private ProfileService profileService;

@Autowired
private CartService cartService;

@Autowired
private InfoService infoService;

@GetMapping("/")
public HomeData getHomePageData(HttpServletRequest request) {
List < Product > products = productService.fetchProducts();
List < NewsItem > news = newsService.fetchNews();
UserProfile userProfile = profileService.fetchUserProfile(request);
Cart cart = cartService.fetchCart(request);
DebugInfo technicalInfo = infoService.fetchTechnicalInfo();
return new HomeData(products, news, userProfile, cart, technicalInfo);
}
// …
}

When to Use BFF and When to Avoid It

The best scenario where BFF works correctly is when you are working with multiple front-end clients, each with unique data requirements.

However, if you are dealing with single-client projects, there is no need to implement BFF in your code, as this is no different from over-engineering.

The Evolution of System Complexity

Traditional web applications handled requests from browsers to web app endpoints, but the emergence of mobile clients disrupted this simplicity. Mobile devices, with their varying screen sizes and bandwidth constraints, introduced a new set of challenges.

Consider a scenario where an e-commerce platform serves both mobile and desktop clients. While the desktop client can comfortably display a wide range of product information, the mobile client, with its limited screen real estate and potentially slower network connectivity, needs a leaner dataset for an optimal user experience.

In simple words, we do not want to send unnecessary data to our clients, since it is not a good practice for several reasons.

BFF for Mobile Clients:

@RestController
public class MobileBffController {

@Autowired
private ProductService productService;

@Autowired
private NewsService newsService;

@GetMapping("/")
public HomeDataForMobile getHomePageData() {
List < Product > products = productService.fetchMobileProducts();
List < NewsItem > news = newsService.fetchMobileNews();
return new HomeDataForMobile(products, news);
}
// …
}

BFF for Desktop Clients:

@RestController
public class DesktopBffController {

@Autowired
private ProductService productService;

@Autowired
private NewsService newsService;

@GetMapping("/")
public HomeDataForDesktop getHomePageData() {
List < Product > products = productService.fetchDesktopProducts();
List < NewsItem > news = newsService.fetchDesktopNews();
return new HomeDataForDesktop(products, news);
}
// …
}

What Problems Does BFF Solve?

The BFF pattern effectively tackles the issues of data fetching to the clients. We may either over-fetch or under-fetch our data, maybe even without knowingly. With this kind of situation, we may have face up with performance issues leading to bottlenecks, or insufficient data, resulting in bad app functionality. BFF ensures the right balance by tailoring data to the specific needs of each client.

Imagine, we have a catalog microservice as below:

Product Catalog Service

@RestController
public class ProductController {

@GetMapping("/products")
public List < Product > getProducts() {
// Fetch products from the catalog microservice
return productService.fetchProducts();
}
// …
}

Now, let’s say we also have a Newsfeed microservice as below:

Newsfeed Service

@RestController
public class NewsController {

@GetMapping("/news")
public List < NewsItem > getNews() {
// Fetch news items from the newsfeed microservice
return newsService.fetchNews();
}
// …
}

In the end, we will make requests to these microservices, get the data as filtered, or we will filter it after we get the data, and return the necessary objects with only the necessary info in it to our clients. Resulting in no over-fetching or under-fetching data.

Creating the BFF

One of the most common problems in microservice architecture is the collection of data with different requests in different services.

You may directly collect all the data you need by making one request to one service, which happens to make necessary calls to all necessary parts collects data on the way, and returns it back to you, which is not the best way, obviously.

Or in a better way, you may get all the necessary info by making a bunch of requests to different parts of your architecture. In this case, you will be facing a problem that requires you to merge the requested responses and build up one and only response object.

These situations can be handled best with a BFF architecture.

Performance Considerations with BFF

Performance is a critical aspect of any architectural decision. The BFF approach minimizes the number of requests made by clients, resulting in a smoother user experience.

In a monolithic architecture, the client makes a request that takes time T seconds, whereas microservices require multiple sequential or parallel calls, significantly increasing response times, let’s say M seconds.

BFF brings us back to a single request, with a few additional requests to microservices. While the overall time may be slightly longer than a monolith, it doesn’t substantially impact the user experience.

Where T < Z < M, we may say BFF makes the calls in Z seconds.

Conclusion

In the ever-evolving landscape of software development, the Backend for Front-End (BFF) pattern has emerged as a versatile solution to address the challenges posed by complex system architectures. By creating a dedicated layer that tailors data to the unique needs of each front-end client, BFF streamlines data delivery and enhances the user experience.

Whether you’re dealing with multiple front-end clients, migrating to microservices, or looking to enhance control at the API Gateway level, BFF provides the flexibility and efficiency required in modern application development.

Images taken from: https://dzone.com/articles/discussing-backend-for-front-end & Twitter

Stackademic

Thank you for reading until the end. Before you go:

--

--