The “Backend for Frontend” is a design pattern used when developing application architecture that relies on microservices and is essentially a variant of the API Gateway pattern.
Before diving into the subject and analyzing the sample application architecture, let’s briefly cover the design patterns for microservices.
The simplest one is the “direct pattern.”
In this approach, a client makes a direct request to microservices, and each microservice has a public endpoint (URL) with which the client can communicate.
While this design is easy to implement and can work for small applications, it will generate problems when it becomes more complex.
You can have performance issues because a section of your app may have multiple calls to different microservices, leading to increased latency. Also, any change/bug you produce on a microservice can result in an application failure.
Also, the endpoints are exposed, and while this doesn’t necessarily create a security issue, it will be harder to take care of application security.
So, the next step in evolution is to have the “API Gateway” design pattern.
This design pattern introduces an extra layer between the client and microservices. Now, we have a single entry point between the front end and our services.
The newly introduced gateway abstracts the references to microservices and hides the endpoints from the public. Also, it can reduce latency by aggregating multiple service calls.
However, all of our problems still need to be solved. For example, the API gateway can become bloated because it will have to serve requests from different client apps. And it can also turn into a monolithic app, which we are trying to avoid from the start.
Introducing “Backend for Front End”
In this case, rather than having a simple point of entry, we introduce multiple gateways that accommodate various types of clients or particular business domains.
This model is perfect for delivering large-scale microservices and is much easier to manage.
Let’s examine a simple example to see how this pattern can improve an application.
Let’s assume we have a classic e-commerce app designed using the direct pattern. In this app, we have the basket module that shows what products are in a client basket.
So in the case of direct architecture, this module will have to
- Make a call to get the product list API microservice and will receive a list of ids.
- For each id, it would have to call the get_product_in API to get product data.
- Consume all the data received and prepared for the front end (removing unnecessary data, formatting the data for a display, etc)
So in the case of direct pattern, you will need to orchestrate many API calls before you get the final data format. And consider that each frontend application may have different requirements and require other logic. (think web vs. mobile )
Now let’s look over this basket module done in a BFF way.
We will have the middle API gateway between microservices and front-end applications. In this case, the front-end application will make a single call to BFF API instead of calling all the microservices.
The BFF layer will then call all the microservices and structure and format the responses before replying with a formatted response.
The BFF API gateway is focused on a single UI and only that UI. As a result, it will keep the front end much more straightforward and lighter. And Front End Apps will become much easier to maintain, and because they know less about your API structure, they will become more resilient to API changes.
Let’s look over a more complicated architecture that uses the BFF pattern. In this case, we are trying to improve the client experience by getting real-time updates. The architecture shows how apps can implement the BFF pattern to load UI-ready data and refresh the visual interface with event-driven notifications.
In the “Microservices Events” block, we have “events consumers” that continuously handle various events generated by the application. Those “event consumers” update a table in Dynamo.
When the user loads the application, it will authenticate via Cognito and query the data using the BFF API built with AWS API Gateway. The API will use a direct DynomoDB call or a Lambda to query the data.
Simultaneously, the client subscribes for any subsequent data changes via a BFF Webscocket endpoint, which will update the “client connected” DynamoDB table.
When the data is changed in the application database tables, a DynamoDB Stream will capture the item level modifications and trigger a Lambda function to handle the data stream.
This lambda will check the client’s table to see if a particular client is connected and, if yes, will push the notification via BFF WebSocket.
After that, the user interface will react according to the notifications received from the API.