What do you get when you have a lot of Microservices to fetch data from before displaying a result — Multiple network calls; add the need to display results based on the requesting client and it becomes a dilemma.
Solution? Backends For Frontends(BFF)
In this article, we would get to know about what the Backends For Frontends pattern is about, why it came about, the problems it seeks to solve when to use it, and its rewards. Let's get right into it!
Initially, applications were built using the Monolith architecture approach. Here all application components are bundled and deployed as a single unit.
One single source code contains the application and handles everything and is hosted on a server, clients connect to this server and get results.
This single application contains the APIs, UI, Data Access Layers, CronJobs, etc all in one. This brought some advantages:
Quick deployment: Simple and faster deployment
Easier testing: Since the whole source code resides in one place, it makes it easier to debug and carry out automated tests.
Faster Development: Since there are fewer moving parts, all efforts are focused on a single source code.
However, this approach came with some headaches, because all the technology and services were locked into one single large project and deployed, and software engineering teams, had to deal with issues like:
Poor Fault Tolerance: Once the application goes down, all components are affected.
Technology lockdown: Application built with a single language, even when some parts of the application would have faired better been programmed using another language.
Maintenance: Difficult to maintain as there are a lot of tightly coupled parts, a minor update in a component could lead to a breaking change in another component.
Scalability: The need to scale the whole application which increases the cost when only a particular section of the application requires scaling.
Then came Microservice, where each of the services is broken down into separate services that have a well-defined domain. Clients connect to these microservices, fetch information, and display. This solves the issues experienced in Monolith.
This brought great advantages like:
Improved Fault Tolerance
However as can be seen from the image above, the client now connects to multiple servers to get the information it requires. This adds additional work for the clients as it has to make multiple calls to get the data it needs to display a result, as well as do more data manipulation and formatting on its end to suit what it needs.
This is where Backends For Frontends pattern comes in handy.
It is a layer that sits between the Backend(Microservices) and the client, it acts as an aggregator and an additional layer of abstraction.
Every client be it mobile, desktop, IoT devices, and wearables would have a separate BFF that communicates with the different microservices, filters out unwanted data, and formats the response in a way needed by the client.
The diagram below gotten from Sam Newman's webpage shows a great visual about this pattern in use. He emphasizes the need for having one BFF per user experience.
This improves the performance of the overall application as it reduces network calls the client needs to make before it gets the data it needs.
Reduces network calls from the client side: The Client doesn't need to communicate to multiple endpoints/microservices to get all the data it needs to display. This data aggregating task is now handed over to the BFFs server.
Fewer client data manipulation: Each client BFF handles data fetching, manipulation, and aggregation as needed for the client. This removes the need from the client end to perform this data manipulation to display a unique view to the user.
Security: An additional layer can also act as a filter removing some fields which exist in each microservice endpoint response that may not be needed for a particular client. This further improves the security of the overall architecture.
Separation of concerns: There is a saying that “Backends tend to live longer than Frontends” hence we shouldn't introduce frontend requirements directly into the backend. This makes it easier for maintenance.
Removes the need to update client response when a new API version is released to a microservice: Since the BFFs act as a layer of abstraction, updates to the underlying API(Microservices) endpoint won't require a change on the client end, as the BFF acts as the source for the client and not the API directly. Hence BFF maintains the endpoints response format it provides to the client while it does the update internally on how it communicates with the Microservices due to the newly released API versions.
With all the good intentions this pattern has, it introduces some concerns which are:
Security: One point of attack, since the client only communicates with the BFF, makes it an easier target and once down the client won't be able to fetch the results it needs.
Increase in network latency: Due to the multiple network hops made before a request is sent back to the client, there is some network latency introduced.
Additional Code maintenance: It introduces an additional piece of code that has to be developed, maintained, deployed, and monitored.
The deciding factor here is the client. The moment you need to provide unique functionality for a particular client say a mobile UI, Web App, or third party, then it is strongly advised to consider using BFFs for each client.
That is when the interface between different clients varies significantly, a BFF is your best option.
I added this section because as I learned about the BFF pattern I found some overlap with the API gateway.
“A standard API gateway handles requests from all the clients interacting with the system, while a BFF only handles a specific client.”
The Backends For Frontends is a natural evolution of Microservices Architecture, it’s a pattern created with the clients in mind and also the user's experience.
I look forward to seeing this pattern used more within the tech ecosystem and can't wait to see what you build with it!