What is the relationship between front-end and back-end services and how can we make them scalable and fault tollerant?
A BFF (Backend for frontend) is a design pattern that creates a layer between the frontend and the backend of an application. The BFF acts as a proxy that handles requests from the frontend and communicates with the backend services. The BFF can also perform tasks such as authentication, authorization, caching, data transformation, and error handling.
There are many benefits to having a BFF, here are a few key ones.
It is key to keep in mind that a BFF should never alter or add business logic, it should simply expose it in a frontend friendly way. All business logic changes should be delegated to backend services as they naturally should propagate to the rest of the solution.
Here is an example of a network before (on left) and after (on right) implementing a BFF.
BFF patterns also come with some potential hazards or pitfalls that should be considered before implementing it.
Therefore, we should view the BFF as a decoupled application built on top of our backend network with the goal of streamlining it for consumption. As it is an App on the existing solution, and a backend service for the front end, it can implement its own caching, querying logic and other assisting tools that enable frontend use cases without affecting core system business logic.
An example of a BFF adding their own logic without affecting business logic could be advanced filtering scenarios. A Persona Service contains a group hierarchy and persona objects with varying properties it and either allows retreival of all objects or specific individuals. For an advanced search and filtering capability, the BFF can cache the users to a search platform like Postgres or OpenSearch and enable advanced filtering for the UI. Of course, this will mean it needs to keep this cache up to date, but there are tools for this as we discussed in Event Driven Services and Event Sourcing. ( CQRS and state in Event Driven Services / Read services Composing multiple services into one)
To solve the tight API coupling and performance coupling, we can use the concepts of Event Sourcing and Event Driven Design. If the BFF would decrease the amount of data retrieval it makes from the backend it would be able to function even if the backend was down, it’s requests would be returned faster, and high user activity would not affect the core system performance (the backend).
You might initially try to cache data locally in the BFF for some requests, but you’ll quickly realize it’s difficult to determine if cache misses are due to data genuinely not existing or due to the cache not being up to date.
Thus, you might as well just opt in to building in an event driven model such as an Event Driven Service (Event Driven Services) from the start and save time in converting later. For implementation guidelines look here (
CQRS and state in Event Driven Services). But your output would look like something below.
BFFs would subscribe to Fact Events from other Event Driven Services in the network and build up a local cache using Event Logging and Event Sourcing. All reads would be contained locally to the service. Incoming write requests would be converted to Command Events or performed as direct gRPC requests to ensure direct feedback, for example to get a status code as return.
If the BFF is implemented with Event Sourcing and eventual consistency principals mentioned in the previous chapter, it might lead to the UI basing its rendering on globaly outdated but locally correct data. This might be acceptable for many application types, but some might need the UI to always be up to date as the event horizon approaches.
To adress this we can use the same event driven techniques that Event Driven Services use for the frontend. Event Driven UI is a design pattern that updates the user interface based on events from the server, rather than polling or refreshing the page.
SignalR can help solve this problem by using WebSockets or other fallback techniques to establish a persistent connection between the server and the client. The server can then push events to the client whenever there is a change in the data, such as an update, a deletion, or an insertion, due to an incoming Fact Event. The client can then update the UI accordingly by directly subscribing to Fact Events, without waiting for the BFF’s cache to be refreshed and poll again.
Internal:
External: