BFF: What technical benefits?

Part 2: A Leverage for Better Performances, Security & Productivity

Raphaël Tahar
Decathlon Digital
9 min readOct 18, 2023

--

DALL-E interpretation of a “Basket of fruits in a Klimt style”

📖 Series table of content

This series explores the Backend For Frontend design pattern in 4 different dimensions captured in 4 posts.

  1. Part 1: A Design Pattern Helping Teams Gain Ownership
  2. 👉 Part 2: What technical benefits?
  3. Part 3: How to scale and avoid pitfalls?
  4. Part 4: Alternatives & decision tree

In the previous blog post, we went over BFF benefits regarding team autonomy and organizations’ architectures.

Let’s now take a look at more technical advantages of BFFs!

BFF sub-types & specifications 🏷️

Before jumping on BFF sub-types particularities, let’s state what they all have in common.

They all serve a User Experience.

The user experience (UX) is how a user interacts with and experiences a product, system or service. It includes a person’s perceptions of utility, ease of use, and efficiency.
- Wikipedia

Following that logic, a User Experience is also defined by the nature of the application, since a mobile app serves a different experience than a browser app. Even iOS and Android apps provide distinct experiences (which means a distinct BFF for Android, iOS, and web applications).

So, besides being UX-focused, a BFF should encapsulate purely technical matters. This includes (non-exhaustive):

  • Every custom adaptation regarding data formats (data manipulation)
  • Business logic for reaching sub-services (what APIs to call, how…)
  • Technical display-specific processing (i.e. controlling the resizing and quality of images for display purposes)
  • Client exchange protocol flexibility (WebSockets, GraphQL, etc.)
  • Even authentication (but not authorization; this is usually a core business topic)

To sum up, the best practice is to create a BFF per User Experience to maximize teams’ autonomy. Of course, there is a risk of code duplication but only try to mutualize BFFs if it creates an identified and desired additional value (Mutualizing exposes to a high risk of Wet Codebases, which can lead to various negative impacts).

Alright, now let’s peruse the existing sub-types!

01 The Backend Proxy Orchestrator’s mission is to call different backend services to retrieve the data required by the frontend. Its main objective is to obfuscate backend business logic or components and prevent any backend reference from being loaded client-side (security through obscurity).

The Backend Proxy Orchestrator

To do that, BFF centralizes the orchestration of backend service calls and then aggregates and filters/formats responses for a tailor-fit data structure response.

Finally, the client application consumes and casts the data it received into pieces of information thanks to rendering components that exclusively live within the browser’s realm.

This sub-type lowers client-side network usage since only one network call is performed between the client and the server before building a payload that perfectly fits the client’s needs, thus limiting over-fetch overheads.

BFFs are also a good match for slow network devices or regions.

Regarding databases, BFFs should be considered as orchestrators. This means that they shouldn’t directly use Databases to handle core business logic.

02 The Frontend Proxy Orchestrator offers a backend playground for frontend applications. Same as for the backend proxy, it obfuscates business logic but this time it prevents frontend business logic and sensitive pieces of information from reaching any browsers (heavy dependencies can be executed backend-side, and thus not loaded client-side, or sensitive data such as API keys can stay backend-side).

The Frontend Proxy Orchestrator

This type of BFF can fetch the data it needs directly from the backend side without any component being rendered frontend side. To do so, Server-Side Rendered components are run (backend side) and hydrated with the fetched data to build up an HTML document to be sent back to the client.

This drastically limits the JavaScript that needs to run client-side which makes the content accessible for web crawlers and is thus SEO-friendly. Note that the Server-Side Rendering (SSR) engine you pick will significantly impact the quantity of JavaScript that will or won’t reach end-users’ browsers.

Again, BFFs should be considered as orchestrators. This means that they shouldn’t directly use Databases to handle core business logic.

PS: Web crawlers have made tremendous progress, so expect this SSR = SEO statement to probably soon become outdated.

PPS: Server-side rendering is not a silver bullet. Its dynamic nature can come with significant computing overhead costs.
Many server-side rendering solutions don’t flush early, and can delay TTFB or double the amount of data sent (for example, the inlined state transmited as a CDATA HTML element used by some SSR frameworks to rehydrate frontend application client-side).

03 The Authentifier Middleware is an edge case at the frontier of BFFs’ scope.

The Authentifier Middleware

Its objective is to increase a client application’s level of security by preventing sensitive pieces of information from reaching the browser and being potentially intercepted. This intermediate layer plays the role of a middleware that handles critical authentication processes.

For example, in an OAuth architecture, it stores the refresh and access tokens and provides a session token to the client that limits, even more, the surfaces for potential attacks.

Indeed, this architecture is specific as it usually embeds a Database, which is, for some, the definition of (micro)services (a service and a DB working in partial autonomy). But using a BFF with a Database is not an issue if it serves an experience and handles purely technical frontend issues.

Note that these 3 BFF sub-types are not mutually exclusive. It is totally possible to combine all 3 within a single BFF.

Purer Codebases ✨ optimized bundle size 📦 & network usage 🛜

BFFs also unlock cleaner codebase architecture by encapsulating business logic and preventing leaks from one domain into another.

Client applications have no choice but to be Client Side Rendered without BFF. Regarding the separation of concerns, the entire data fetching and pre/post-processing business logic must be defined inside the frontend codebase and executed in the browser.

No BFF: Business Logic location

This model often leads to leaky business logic that should have been implemented within microservices but ends up within client applications because teams owning the microservices don’t have the time or the budget to perform those changes.

This is a poor design regarding the following:

  • High chances of spaghetti code (data fetching & processing mixed in rendering components or state management libs)
  • Security (as microservices references are defined in code loaded and run in browsers)
  • Network costs (more JavaScript code transits over the network)
  • Bigger bundle size (more JavaScript code fetched/interpreted/compiled)
  • Inconsistent user experience (performances vary depending on the client’s terminal hardware and network quality)

A BFF counters most of these downsides.

With BFF: Business Logic location

With a BFF architecture, only pure rendering business logic is loaded client-side (it can drastically decrease downloaded bundle sizes), and doesn’t embed any sensitive references to backend bricks (security by obfuscation). Only the BFF knows the backend layers it must orchestrate.

Moreover, since two distinct artifacts are built and deployed, the risks of business logic leaks, spaghetti code, and security issues are highly lowered.

On the other hand, a unique call is performed per page, limiting:

  • the application memory footprint (no more over-fetching)
  • the CPU-intensive operations in-browser (less user experience inconsistencies)
  • and the network usage (less JS code downloaded).

End-to-end type safety 🦺

Another benefit of using the BFF pattern revolves around types and cross-app type safety.

Back to our product page use case, consider the following type as the schema expected by the client application:

type ProductPage = {
cart: Cart,
productList: ProductList,
recommendations: Recommendations,
}

In a “traditional” Client Side Rendered without BFF architecture, types’ definitions are spread across multiple applications. No single source of truth opens the door to errors and inconsistencies.

No BFF: Types are not shared

As we’ve seen in the previous blog post, BFFs highly decrease the cost of changing in exchange for a higher cost of building and owning.

Let’s see how we could mitigate this with another architectural design pattern: the Monorepo.

A Monorepo is defined by multiple applications collocated in a single repository with rules and tools that enforce boundaries.
(⚠️ Caution Monorepo ≠ Monolith. If you want to know more about this perception shift, take a look at this series I wrote to demystify Monorepos).

So, using a Monorepo to hold your client and BFF applications would unlock end-to-end type safety with a shared and unique definition of your application types.

Since the two applications are collocated in a unique repository, you can define your types once and import them into your applications at build time.

With BFF: Interface types unicity shared at build time & run-time data validation

Aside from sharing types to safeguard your development flow at build time, you can also extrapolate JSON Schema from those types to execute run-time validation upon the data received or sent by your BFF or frontend applications (i.e. check out Zod that implements an infer method to generate a validation schema based on typescript types).

It also brings another productivity benefit since it would allow full-stack autocompletion within your IDEs (lowered cost of change & build).

Going the extra mile!

End-to-end type-safety with a minimized cost of build & change

To enhance type safety even more, you could also use DTOs (Data Transfer Objects) within your BFF to type-check data exchanges with backend microservices at run time. This enables safer data serialization when services communicate with each other. Indeed, when two services communicate, the sending service casts data from in-memory objects to bytes (as strings) before sending it, leaving the receiving service the care to cast it back into the proper object types before using it (strings cast into string variables, numbers expressed as strings into numbers and more complex non-primitive types such as Dates cast from strings to date objects).

To lower even more the cost of changing and building, DTOs can also be auto-generated from the types attached to your BFF backend routes (as always, automated tasks are suitable for everyday use cases but can become tricky around the edges, so expect a different developer experience per functional use case).

So, by combining BFFs, Monorepo, and DTOs, a team can better control their application’s type safety without adding too much maintainability burden and increase their developer experience with full-stack autocompletion.

How about you?!

Have you discovered other BFF benefits?
Please let us know in the comments!
Gathering more insights and use cases might be very educational.

Conclusion

To sum up, the BFF pattern has various virtues:

  • Optimized payloads (less network calls & latency)
  • Optimized client-side memory footprint (no more over-fetching)
  • Optimized CPU performances (lesser data normalization/denormalization)
  • Purer codebases (less business logic leaked into the frontend application)
  • Enhanced security (session rather than refresh tokens within client runtime)
  • The cherry on top is end-to-end type safety and full-stack autocompletion (if the client and BFF code bases are collocated in a single repository)

But it also has challenges and pitfalls. In this series’s next post, we’ll highlight these challenges and extract a list of best practices to avoid common traps.

Thanks for reading! 🙏🏼
👏🏻👏🏻👏🏻 Give a few claps and “
follow” if you enjoyed this series.

💌 Follow our latest posts on Twitter and LinkedIn and discover our latest stories on Medium 🚀

Acknowledgments
And a big thank you to Jérome Molière, Laurent Thiebault, Alexandre Faria, and Ramzi ACHOURI for their thorough reviews and feedback. Thanks guys!

--

--

Raphaël Tahar
Decathlon Digital

Staff Engineer, Chapter Lead and philosophy enthusiast. Proud dog father 🐶. Opinions are my own.