Tale of Software Architect(ure): Part 15 (Backend for Frontend (BFF) Architecture Pattern)
Saiful Islam Rasel
Senior Engineer, SDE @ bKash | Ex: AsthaIT | Sports Programmer | Problem Solver | FinTech | Microservice | Java | Spring-boot | C# | .NET | PostgreSQL | DynamoDB | JavaScript | TypeScript | React.js | Next.js | Angular
Story:
In the land of AppVille, there lived two very different characters: Mobi, the energetic mobile app, and Webby, the sophisticated web app. Both served the people of AppVille by connecting them to the Kingdom of Data, a realm where all the important information, user profiles, posts, and notifications was stored.
At first, Mobi and Webby relied on the same Royal Backend, a single source of truth for all things data. But there was a problem. The Royal Backend wasn’t tailored for either of them. It treated Mobi and Webby the same, sending them the same information in the same way. This caused a lot of frustration.
One day, the Wise Architect of AppVille realized this wasn’t working. “Mobi and Webby are too different,” the Architect said. “They need their own companions who understand their unique needs.” And so, the BFFs (Backend for Frontend) were born.
Now, whenever Mobi or Webby needed something from the Kingdom of Data, they didn’t go directly to the Royal Backend anymore. They asked their trusted BFFs. With the help of their BFFs, Mobi and Webby thrived. And so, Mobi, Webby, and their BFFs lived happily.
Backend for Frontend (BFF) Architecture Pattern:
Backend for Frontend (BFF) architecture is a design pattern in which separate backend services are created specifically to serve the needs of different frontend applications (e.g., mobile apps, web apps, etc.). Instead of having a single, monolithic backend that tries to serve all kinds of clients, each frontend has its own tailored backend service, allowing for better optimization and separation of concerns. This pattern is useful when you want to avoid customizing a single backend for multiple interfaces. This pattern was first described by Sam Newman.
Key Components of BFF Architecture
Why BFF is Useful
Practical Example
Let’s say we’re building a social media platform with both a mobile app and a web app. Here’s how the BFF architecture might work:
1. Web App BFF
2. Mobile App BFF
Context:
In modern software systems, multiple frontend applications often interact with the same backend services. These frontends can include: Web applications (desktop browsers), Mobile applications (iOS, Android), Other clients (e.g., IoT devices, smartwatches)
Each frontend typically has unique requirements, such as:
In a traditional monolithic backend or a common API shared by all frontends, the backend may struggle to serve the unique needs of each frontend effectively. This often leads to:
Problem:
A shared, one-size-fits-all backend architecture results in challenges such as:
Solution:
The Backend for Frontend (BFF) design pattern addresses the problem by introducing custom backends for each frontend type. Each frontend (e.g., web app, mobile app) interacts with its own tailored backend service, which is responsible for aggregating data, handling business logic, and optimizing performance specific to that frontend’s needs.
How the BFF architecture solves the problem
Sample Pseudocode:
Here’s a sample pseudocode example illustrating the Backend for Frontend (BFF) architecture for a social media platform with separate BFFs for a mobile app and a web app.
Scenario
Core Backend Microservices
1. Core Backend Microservices
class UserService:
def getUserDetails(userId):
# Fetch user details from database
return {
"id": userId,
"name": "Alice",
"profilePicture": "high_res_image.jpg",
"bio": "Avid photographer and traveler",
"followers": 1200
}
class PostService:
def getUserFeed(userId):
# Fetch user's posts from database
return [
{ "postId": 1, "content": "Check out my new photo!", "image": "high_res_photo1.jpg" },
{ "postId": 2, "content": "Exploring the mountains!", "image": "high_res_photo2.jpg" }
]
class NotificationService:
def getUnreadNotifications(userId):
# Fetch unread notifications for the user
return [
{ "notificationId": 101, "message": "New follower: John" },
{ "notificationId": 102, "message": "Your post got 20 likes!" }
]
2. Mobile App BFF
The mobile BFF fetches lightweight data (e.g., smaller images, fewer details) to optimize for mobile performance.
class MobileAppBFF:
def getMobileUserProfile(userId):
userDetails = UserService.getUserDetails(userId)
userFeed = PostService.getUserFeed(userId)
notifications = NotificationService.getUnreadNotifications(userId)
# Modify user details for mobile (e.g., compress image, reduce bio length)
userDetails["profilePicture"] = compressImage(userDetails["profilePicture"])
userDetails["bio"] = truncate(userDetails["bio"], 50)
# Modify feed for mobile (e.g., use thumbnails instead of full images)
for post in userFeed:
post["image"] = createThumbnail(post["image"])
# Return the optimized data for the mobile app
return {
"user": {
"id": userDetails["id"],
"name": userDetails["name"],
"profilePicture": userDetails["profilePicture"],
"bio": userDetails["bio"]
},
"feed": userFeed,
"notifications": notifications
}
def compressImage(imageUrl):
# Logic to compress image for mobile
return "compressed_" + imageUrl
def createThumbnail(imageUrl):
# Logic to create thumbnail for mobile
return "thumbnail_" + imageUrl
def truncate(text, length):
# Truncate text to a specific length
return text[:length] + "..."
3. Web App BFF
The web BFF fetches richer content and larger images, as the web app can handle more detailed information.
class WebAppBFF:
def getWebUserProfile(userId):
userDetails = UserService.getUserDetails(userId)
userFeed = PostService.getUserFeed(userId)
notifications = NotificationService.getUnreadNotifications(userId)
# Return full-size images and complete user data
return {
"user": {
"id": userDetails["id"],
"name": userDetails["name"],
"profilePicture": userDetails["profilePicture"], # Full-size image
"bio": userDetails["bio"], # Full bio
"followers": userDetails["followers"]
},
"feed": userFeed, # Full-size images in posts
"notifications": notifications
}
Sample API Response:
1. Mobile App Response (via MobileAppBFF)
{
"user": {
"id": 1,
"name": "Alice",
"profilePicture": "compressed_high_res_image.jpg",
"bio": "Avid photographer..."
},
"feed": [
{ "postId": 1, "content": "Check out my new photo!", "image": "thumbnail_high_res_photo1.jpg" },
{ "postId": 2, "content": "Exploring the mountains!", "image": "thumbnail_high_res_photo2.jpg" }
],
"notifications": [
{ "notificationId": 101, "message": "New follower: John" },
{ "notificationId": 102, "message": "Your post got 20 likes!" }
]
}
2. Web App Response (via WebAppBFF)
{
"user": {
"id": 1,
"name": "Alice",
"profilePicture": "high_res_image.jpg",
"bio": "Avid photographer and traveler",
"followers": 1200
},
"feed": [
{ "postId": 1, "content": "Check out my new photo!", "image": "high_res_photo1.jpg" },
{ "postId": 2, "content": "Exploring the mountains!", "image": "high_res_photo2.jpg" }
],
"notifications": [
{ "notificationId": 101, "message": "New follower: John" },
{ "notificationId": 102, "message": "Your post got 20 likes!" }
]
}
Summary:
Backend for Frontend (BFF) architecture is a design pattern where separate backend services (BFFs) are created for different frontend applications (e.g., mobile apps, web apps). Each BFF is tailored to meet the specific needs of its corresponding frontend, optimizing data handling, performance, and user experience.
It gives you: Frontend-Specific Backends, Optimized Performance, Decoupling, Improved Scalability etc.
By separating backends for each frontend, the BFF architecture allows for more efficient, flexible, and maintainable systems.