🎯 Client-Specific
BFF provides tailored APIs for each client type. Mobile gets minimal data, web gets full features.
Traditional approach: One API for all clients.
Problems:
Solution: Backend for Frontend (BFF) - One backend per frontend!
BFF = Each client type gets its own tailored backend API.
Each BFF:
Mobile needs:
1{2 "id": 123,3 "name": "John",4 "avatar": "small_url"5}Size: ~100 bytes
Web needs:
1{2 "id": 123,3 "name": "John Doe",4 "email": "john@example.com",5 "avatar": "large_url",6 "bio": "Long bio text...",7 "preferences": {...},8 "recentActivity": [...],9 "friends": [...]10}Size: ~5000 bytes
Without BFF: Mobile gets 5000 bytes (wastes 4900 bytes!)
With BFF: Mobile gets 100 bytes (50x smaller!)
Each client has dedicated BFF:
Pros:
Cons:
One BFF with client-specific adapters:
Pros:
Cons:
1from flask import Flask, jsonify2import requests3
4app = Flask(__name__)5
6SERVICES = {7 'users': 'http://user-service:8001',8 'orders': 'http://order-service:8002',9}10
11@app.route('/api/mobile/users/<user_id>')12def get_mobile_user(user_id):13 """Mobile-optimized user endpoint - minimal data"""14 # Fetch from user service15 user_response = requests.get(f"{SERVICES['users']}/users/{user_id}")16 user = user_response.json()17
18 # Transform for mobile (minimal data)19 mobile_user = {20 'id': user['id'],21 'name': user['name'],22 'avatar': user.get('avatar_small'), # Small avatar for mobile23 }24
25 return jsonify(mobile_user)26
27@app.route('/api/mobile/users/<user_id>/orders')28def get_mobile_orders(user_id):29 """Mobile-optimized orders - summary only"""30 orders_response = requests.get(31 f"{SERVICES['orders']}/orders?userId={user_id}"32 )33 orders = orders_response.json()34
35 # Transform for mobile (summary only)36 mobile_orders = [37 {38 'id': order['id'],39 'total': order['total'],40 'status': order['status'],41 'date': order['created_at'][:10] # Just date, not full timestamp42 }43 for order in orders44 ]45
46 return jsonify(mobile_orders)1import org.springframework.web.bind.annotation.*;2import org.springframework.web.client.RestTemplate;3
4@RestController5@RequestMapping("/api/mobile")6public class MobileBFF {7 private final RestTemplate restTemplate;8 private final String userServiceUrl = "http://user-service:8001";9 private final String orderServiceUrl = "http://order-service:8002";10
11 @GetMapping("/users/{userId}")12 public MobileUserDTO getMobileUser(@PathVariable String userId) {13 // Fetch from user service14 User user = restTemplate.getForObject(15 userServiceUrl + "/users/" + userId, User.class16 );17
18 // Transform for mobile (minimal data)19 return MobileUserDTO.builder()20 .id(user.getId())21 .name(user.getName())22 .avatar(user.getAvatarSmall()) // Small avatar for mobile23 .build();24 }25
26 @GetMapping("/users/{userId}/orders")27 public List<MobileOrderDTO> getMobileOrders(@PathVariable String userId) {28 // Fetch from order service29 List<Order> orders = restTemplate.getForObject(30 orderServiceUrl + "/orders?userId=" + userId,31 List.class32 );33
34 // Transform for mobile (summary only)35 return orders.stream()36 .map(order -> MobileOrderDTO.builder()37 .id(order.getId())38 .total(order.getTotal())39 .status(order.getStatus())40 .date(order.getCreatedAt().toString().substring(0, 10))41 .build())42 .collect(Collectors.toList());43 }44}1from flask import Flask, jsonify2import requests3
4app = Flask(__name__)5
6SERVICES = {7 'users': 'http://user-service:8001',8 'orders': 'http://order-service:8002',9 'products': 'http://product-service:8003',10}11
12@app.route('/api/web/users/<user_id>')13def get_web_user(user_id):14 """Web-optimized user endpoint - full data"""15 # Fetch from multiple services16 import concurrent.futures17
18 with concurrent.futures.ThreadPoolExecutor() as executor:19 user_future = executor.submit(20 requests.get, f"{SERVICES['users']}/users/{user_id}"21 )22 orders_future = executor.submit(23 requests.get, f"{SERVICES['orders']}/orders?userId={user_id}"24 )25 preferences_future = executor.submit(26 requests.get, f"{SERVICES['users']}/users/{user_id}/preferences"27 )28
29 user = user_future.result().json()30 orders = orders_future.result().json()31 preferences = preferences_future.result().json()32
33 # Transform for web (full data)34 web_user = {35 'id': user['id'],36 'name': user['name'],37 'email': user['email'],38 'avatar': user.get('avatar_large'), # Large avatar for web39 'bio': user.get('bio'),40 'preferences': preferences,41 'recentOrders': orders[:5], # Recent orders42 'stats': {43 'totalOrders': len(orders),44 'totalSpent': sum(o['total'] for o in orders),45 }46 }47
48 return jsonify(web_user)1import org.springframework.web.bind.annotation.*;2import org.springframework.web.client.RestTemplate;3import reactor.core.publisher.Mono;4
5@RestController6@RequestMapping("/api/web")7public class WebBFF {8 private final WebClient webClient;9
10 @GetMapping("/users/{userId}")11 public Mono<WebUserDTO> getWebUser(@PathVariable String userId) {12 // Fetch from multiple services in parallel13 Mono<User> user = webClient.get()14 .uri("http://user-service:8001/users/{userId}", userId)15 .retrieve()16 .bodyToMono(User.class);17
18 Mono<List<Order>> orders = webClient.get()19 .uri("http://order-service:8002/orders?userId={userId}", userId)20 .retrieve()21 .bodyToFlux(Order.class)22 .collectList();23
24 Mono<Preferences> preferences = webClient.get()25 .uri("http://user-service:8001/users/{userId}/preferences", userId)26 .retrieve()27 .bodyToMono(Preferences.class);28
29 // Combine and transform for web30 return Mono.zip(user, orders, preferences)31 .map(tuple -> {32 User u = tuple.getT1();33 List<Order> o = tuple.getT2();34 Preferences p = tuple.getT3();35
36 return WebUserDTO.builder()37 .id(u.getId())38 .name(u.getName())39 .email(u.getEmail())40 .avatar(u.getAvatarLarge()) // Large avatar for web41 .bio(u.getBio())42 .preferences(p)43 .recentOrders(o.stream().limit(5).collect(Collectors.toList()))44 .stats(Stats.builder()45 .totalOrders(o.size())46 .totalSpent(o.stream().mapToDouble(Order::getTotal).sum())47 .build())48 .build();49 });50 }51}Adapters transform data for each client:
1from abc import ABC, abstractmethod2from typing import Dict, Any3
4class UserAdapter(ABC):5 """Base adapter interface"""6
7 @abstractmethod8 def transform(self, user: Dict[str, Any]) -> Dict[str, Any]:9 """Transform user data for specific client"""10 pass11
12class MobileUserAdapter(UserAdapter):13 """Adapter for mobile - minimal data"""14
15 def transform(self, user: Dict[str, Any]) -> Dict[str, Any]:16 return {17 'id': user['id'],18 'name': user['name'],19 'avatar': user.get('avatar_small'),20 }21
22class WebUserAdapter(UserAdapter):23 """Adapter for web - full data"""24
25 def transform(self, user: Dict[str, Any]) -> Dict[str, Any]:26 return {27 'id': user['id'],28 'name': user['name'],29 'email': user['email'],30 'avatar': user.get('avatar_large'),31 'bio': user.get('bio'),32 'preferences': user.get('preferences', {}),33 'stats': user.get('stats', {}),34 }35
36class BFFService:37 """BFF service using adapters"""38
39 def __init__(self, adapter: UserAdapter):40 self.adapter = adapter41
42 def get_user(self, user_id: str) -> Dict[str, Any]:43 # Fetch from backend service44 user = user_service.get_user(user_id)45
46 # Transform using adapter47 return self.adapter.transform(user)1interface UserAdapter {2 UserDTO transform(User user);3}4
5class MobileUserAdapter implements UserAdapter {6 @Override7 public UserDTO transform(User user) {8 return MobileUserDTO.builder()9 .id(user.getId())10 .name(user.getName())11 .avatar(user.getAvatarSmall())12 .build();13 }14}15
16class WebUserAdapter implements UserAdapter {17 @Override18 public UserDTO transform(User user) {19 return WebUserDTO.builder()20 .id(user.getId())21 .name(user.getName())22 .email(user.getEmail())23 .avatar(user.getAvatarLarge())24 .bio(user.getBio())25 .preferences(user.getPreferences())26 .stats(user.getStats())27 .build();28 }29}30
31class BFFService {32 private final UserAdapter adapter;33 private final UserService userService;34
35 public BFFService(UserAdapter adapter, UserService userService) {36 this.adapter = adapter;37 this.userService = userService;38 }39
40 public UserDTO getUser(String userId) {41 User user = userService.getUser(userId);42 return adapter.transform(user);43 }44}| Feature | API Gateway | BFF |
|---|---|---|
| Purpose | Routing, cross-cutting concerns | Client-specific optimization |
| Aggregation | Can aggregate | Always aggregates |
| Transformation | Minimal | Heavy transformation |
| Client Awareness | Generic | Client-specific |
| Use Case | Single entry point | Client optimization |
They can work together:
1Client → API Gateway → BFF → Services🎯 Client-Specific
BFF provides tailored APIs for each client type. Mobile gets minimal data, web gets full features.
⚡ Performance Optimized
Each BFF optimizes for its client. Mobile saves bandwidth, web gets rich data.
🔄 Adapter Pattern
Use adapter pattern to transform data for each client. Shared logic, client-specific transformations.
🏗️ Code Reuse
Share common logic in libraries. BFFs can share code while providing client-specific APIs.