🎯 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:
{ "id": 123, "name": "John", "avatar": "small_url"}Size: ~100 bytes
Web needs:
{ "id": 123, "name": "John Doe", "avatar": "large_url", "bio": "Long bio text...", "preferences": {...}, "recentActivity": [...], "friends": [...]}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:
from flask import Flask, jsonifyimport requests
app = Flask(__name__)
SERVICES = { 'users': 'http://user-service:8001', 'orders': 'http://order-service:8002',}
@app.route('/api/mobile/users/<user_id>')def get_mobile_user(user_id): """Mobile-optimized user endpoint - minimal data""" # Fetch from user service user_response = requests.get(f"{SERVICES['users']}/users/{user_id}") user = user_response.json()
# Transform for mobile (minimal data) mobile_user = { 'id': user['id'], 'name': user['name'], 'avatar': user.get('avatar_small'), # Small avatar for mobile }
return jsonify(mobile_user)
@app.route('/api/mobile/users/<user_id>/orders')def get_mobile_orders(user_id): """Mobile-optimized orders - summary only""" orders_response = requests.get( f"{SERVICES['orders']}/orders?userId={user_id}" ) orders = orders_response.json()
# Transform for mobile (summary only) mobile_orders = [ { 'id': order['id'], 'total': order['total'], 'status': order['status'], 'date': order['created_at'][:10] # Just date, not full timestamp } for order in orders ]
return jsonify(mobile_orders)import org.springframework.web.bind.annotation.*;import org.springframework.web.client.RestTemplate;
@RestController@RequestMapping("/api/mobile")public class MobileBFF { private final RestTemplate restTemplate; private final String userServiceUrl = "http://user-service:8001"; private final String orderServiceUrl = "http://order-service:8002";
@GetMapping("/users/{userId}") public MobileUserDTO getMobileUser(@PathVariable String userId) { // Fetch from user service User user = restTemplate.getForObject( userServiceUrl + "/users/" + userId, User.class );
// Transform for mobile (minimal data) return MobileUserDTO.builder() .id(user.getId()) .name(user.getName()) .avatar(user.getAvatarSmall()) // Small avatar for mobile .build(); }
@GetMapping("/users/{userId}/orders") public List<MobileOrderDTO> getMobileOrders(@PathVariable String userId) { // Fetch from order service List<Order> orders = restTemplate.getForObject( orderServiceUrl + "/orders?userId=" + userId, List.class );
// Transform for mobile (summary only) return orders.stream() .map(order -> MobileOrderDTO.builder() .id(order.getId()) .total(order.getTotal()) .status(order.getStatus()) .date(order.getCreatedAt().toString().substring(0, 10)) .build()) .collect(Collectors.toList()); }}from flask import Flask, jsonifyimport requests
app = Flask(__name__)
SERVICES = { 'users': 'http://user-service:8001', 'orders': 'http://order-service:8002', 'products': 'http://product-service:8003',}
@app.route('/api/web/users/<user_id>')def get_web_user(user_id): """Web-optimized user endpoint - full data""" # Fetch from multiple services import concurrent.futures
with concurrent.futures.ThreadPoolExecutor() as executor: user_future = executor.submit( requests.get, f"{SERVICES['users']}/users/{user_id}" ) orders_future = executor.submit( requests.get, f"{SERVICES['orders']}/orders?userId={user_id}" ) preferences_future = executor.submit( requests.get, f"{SERVICES['users']}/users/{user_id}/preferences" )
user = user_future.result().json() orders = orders_future.result().json() preferences = preferences_future.result().json()
# Transform for web (full data) web_user = { 'id': user['id'], 'name': user['name'], 'email': user['email'], 'avatar': user.get('avatar_large'), # Large avatar for web 'bio': user.get('bio'), 'preferences': preferences, 'recentOrders': orders[:5], # Recent orders 'stats': { 'totalOrders': len(orders), 'totalSpent': sum(o['total'] for o in orders), } }
return jsonify(web_user)import org.springframework.web.bind.annotation.*;import org.springframework.web.client.RestTemplate;import reactor.core.publisher.Mono;
@RestController@RequestMapping("/api/web")public class WebBFF { private final WebClient webClient;
@GetMapping("/users/{userId}") public Mono<WebUserDTO> getWebUser(@PathVariable String userId) { // Fetch from multiple services in parallel Mono<User> user = webClient.get() .uri("http://user-service:8001/users/{userId}", userId) .retrieve() .bodyToMono(User.class);
Mono<List<Order>> orders = webClient.get() .uri("http://order-service:8002/orders?userId={userId}", userId) .retrieve() .bodyToFlux(Order.class) .collectList();
Mono<Preferences> preferences = webClient.get() .uri("http://user-service:8001/users/{userId}/preferences", userId) .retrieve() .bodyToMono(Preferences.class);
// Combine and transform for web return Mono.zip(user, orders, preferences) .map(tuple -> { User u = tuple.getT1(); List<Order> o = tuple.getT2(); Preferences p = tuple.getT3();
return WebUserDTO.builder() .id(u.getId()) .name(u.getName()) .email(u.getEmail()) .avatar(u.getAvatarLarge()) // Large avatar for web .bio(u.getBio()) .preferences(p) .recentOrders(o.stream().limit(5).collect(Collectors.toList())) .stats(Stats.builder() .totalOrders(o.size()) .totalSpent(o.stream().mapToDouble(Order::getTotal).sum()) .build()) .build(); }); }}Adapters transform data for each client:
from abc import ABC, abstractmethodfrom typing import Dict, Any
class UserAdapter(ABC): """Base adapter interface"""
@abstractmethod def transform(self, user: Dict[str, Any]) -> Dict[str, Any]: """Transform user data for specific client""" pass
class MobileUserAdapter(UserAdapter): """Adapter for mobile - minimal data"""
def transform(self, user: Dict[str, Any]) -> Dict[str, Any]: return { 'id': user['id'], 'name': user['name'], 'avatar': user.get('avatar_small'), }
class WebUserAdapter(UserAdapter): """Adapter for web - full data"""
def transform(self, user: Dict[str, Any]) -> Dict[str, Any]: return { 'id': user['id'], 'name': user['name'], 'email': user['email'], 'avatar': user.get('avatar_large'), 'bio': user.get('bio'), 'preferences': user.get('preferences', {}), 'stats': user.get('stats', {}), }
class BFFService: """BFF service using adapters"""
def __init__(self, adapter: UserAdapter): self.adapter = adapter
def get_user(self, user_id: str) -> Dict[str, Any]: # Fetch from backend service user = user_service.get_user(user_id)
# Transform using adapter return self.adapter.transform(user)interface UserAdapter { UserDTO transform(User user);}
class MobileUserAdapter implements UserAdapter { @Override public UserDTO transform(User user) { return MobileUserDTO.builder() .id(user.getId()) .name(user.getName()) .avatar(user.getAvatarSmall()) .build(); }}
class WebUserAdapter implements UserAdapter { @Override public UserDTO transform(User user) { return WebUserDTO.builder() .id(user.getId()) .name(user.getName()) .email(user.getEmail()) .avatar(user.getAvatarLarge()) .bio(user.getBio()) .preferences(user.getPreferences()) .stats(user.getStats()) .build(); }}
class BFFService { private final UserAdapter adapter; private final UserService userService;
public BFFService(UserAdapter adapter, UserService userService) { this.adapter = adapter; this.userService = userService; }
public UserDTO getUser(String userId) { User user = userService.getUser(userId); return adapter.transform(user); }}| 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:
Client → 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.