El problema sin BFF
Una empresa tiene tres frontends:
- Web (React): pantalla grande, conexion estable, puede pedir muchos datos.
- Movil iOS (Swift): pantalla pequena, conexion 4G inestable, necesita payloads minimos.
- Movil Android (Kotlin): similar a iOS pero con un dashboard ligeramente distinto.
Si todos consumen la misma API (GET /api/productos/):
- La movil recibe campos que no usa (
descripcion_html,referencia_logistica...). Mas bytes, mas bateria. - La web tiene que hacer 3 llamadas (
/productos,/categorias,/promociones) en serie. - Anadir un campo nuevo para iOS rompe el contrato del web.
- Cada cambio en la API obliga a coordinar 3 equipos frontend.
Este acoplamiento se conoce como "one API to rule them all" y rara vez funciona en arquitecturas con multiples clientes muy distintos.
Que es BFF
Backend for Frontend (Sam Newman, 2015): cada frontend tiene una API dedicada que agrega servicios y devuelve exactamente lo que ese frontend necesita.
flowchart LR
WEB[Web React] --> BFF1[BFF Web<br>Django]
IOS[App iOS] --> BFF2[BFF Movil<br>Django]
AND[App Android] --> BFF2
TV[Smart TV] --> BFF3[BFF TV<br>Django]
BFF1 --> SVC1[Servicio Productos]
BFF1 --> SVC2[Servicio Pedidos]
BFF1 --> SVC3[Servicio Recos]
BFF2 --> SVC1
BFF2 --> SVC2
BFF3 --> SVC1
- Cada BFF lo mantiene el equipo frontend correspondiente: web -> BFF web, movil -> BFF movil. Acoplamiento intencional que reduce coordinacion entre equipos.
- El BFF es publico (lo consume el cliente). Los servicios detras son privados (solo accesibles desde la red interna).
BFF frente a API Gateway
| Aspecto | API Gateway | BFF | |---------|------------|-----| | Logica | Routing, autenticacion, throttling | Routing + agregacion + transformacion | | Frontends | Uno o varios genericos | Uno especifico (web, iOS, Android) | | Mantenedor | Equipo plataforma / SRE | Equipo frontend | | Forma de la respuesta | Forwardea tal cual | Adapta al frontend | | Tools comunes | Kong, Tyk, AWS API Gateway, Traefik | Codigo propio (Django, Express, Spring) |
Un API Gateway puede coexistir con BFFs: el Gateway hace TLS y autenticacion globales, los BFFs hacen agregacion. No son mutuamente exclusivos.
Implementar un BFF para web con DRF
Imaginemos un dashboard web que necesita en una sola pantalla: datos del usuario, sus 5 ultimos pedidos, las 10 recomendaciones top y el saldo del monedero.
# bff_web/views.py
import httpx
import asyncio
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response
class DashboardView(APIView):
async def get(self, request):
user_id = request.user.id
token = request.headers.get("Authorization", "")
headers = {"Authorization": token}
async with httpx.AsyncClient(timeout=5.0, headers=headers) as c:
usuario_t = c.get(f"{settings.SVC_USERS}/api/users/{user_id}/")
pedidos_t = c.get(f"{settings.SVC_ORDERS}/api/orders/?user={user_id}&limit=5")
recos_t = c.get(f"{settings.SVC_RECOS}/api/recos/?user={user_id}&limit=10")
saldo_t = c.get(f"{settings.SVC_WALLET}/api/wallet/{user_id}/")
usuario, pedidos, recos, saldo = await asyncio.gather(
usuario_t, pedidos_t, recos_t, saldo_t
)
# Adaptar el shape para el frontend web concreto
return Response({
"usuario": {
"id": usuario.json()["id"],
"nombre": usuario.json()["full_name"],
"avatar": usuario.json()["avatar_url"],
},
"pedidos_recientes": [
{
"id": p["id"],
"fecha": p["created_at"],
"total": p["total_amount"],
"estado": p["status"],
} for p in pedidos.json()["results"]
],
"recomendaciones": recos.json()["items"][:10],
"saldo": {
"actual": saldo.json()["balance"],
"moneda": saldo.json()["currency"],
},
})
Las 4 llamadas se ejecutan en paralelo con
asyncio.gather. Si cada microservicio tarda 100 ms, el BFF tarda ~100 ms en total y devuelve datos pre-formateados al frontend. Sin BFF, el navegador hace 4 round-trips de 100 ms = 400 ms + parsing local.
Implementar un BFF para movil
El movil quiere los mismos conceptos pero MUCHO menos texto. Mismo backend, distinto shape:
# bff_movil/views.py
class DashboardMovilView(APIView):
async def get(self, request):
user_id = request.user.id
token = request.headers.get("Authorization", "")
async with httpx.AsyncClient(headers={"Authorization": token}) as c:
pedidos_t = c.get(f"{settings.SVC_ORDERS}/api/orders/?user={user_id}&limit=3")
saldo_t = c.get(f"{settings.SVC_WALLET}/api/wallet/{user_id}/")
usuario_t = c.get(f"{settings.SVC_USERS}/api/users/{user_id}/")
pedidos, saldo, usuario = await asyncio.gather(pedidos_t, saldo_t, usuario_t)
return Response({
"u": usuario.json()["full_name"][:30],
"s": saldo.json()["balance"],
"p": [
{"id": p["id"][:8], "t": p["total_amount"]}
for p in pedidos.json()["results"]
],
})
Mismas fuentes, payload 80% mas ligero. Y al equipo movil no le importa lo que necesita el equipo web ni viceversa.
Caching en el BFF
Si el dashboard se pide cada vez que el usuario abre la app, aplicamos caching por sesion.
from django.core.cache import cache
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django.views.decorators.vary import vary_on_headers
class DashboardView(APIView):
@method_decorator(vary_on_headers("Authorization"))
@method_decorator(cache_page(60)) # 60s
async def get(self, request):
...
vary_on_headers("Authorization")hace que el cache key dependa del token (cada usuario tiene su entrada). Sin esto, todos los usuarios verian el dashboard del primero. TTL corto (30-60s) suficiente para que un refresco de pantalla no genere fan-out backend.
Autenticacion delegada
El BFF normalmente no autentica al usuario, delega al servicio de identidad:
class JWTBFFAuthentication:
"""Valida el JWT contra el servicio de identidad."""
async def authenticate(self, request):
token = request.headers.get("Authorization", "").replace("Bearer ", "")
if not token:
return None
async with httpx.AsyncClient() as c:
r = await c.get(f"{settings.SVC_AUTH}/verify/", headers={"Authorization": f"Bearer {token}"})
if r.status_code != 200:
raise AuthenticationFailed()
return (r.json(), token)
Cachea el resultado de verify 30s para no llamar a auth en cada peticion.
Resilience: timeouts y circuit breaker
Si un microservicio cae, el BFF debe degradar elegantemente y no bloquear al usuario.
async def _safe(client, url):
try:
r = await client.get(url, timeout=2.0)
if r.status_code >= 500:
return None
return r.json()
except (httpx.TimeoutException, httpx.HTTPError):
return None
class DashboardView(APIView):
async def get(self, request):
async with httpx.AsyncClient() as c:
usuario, pedidos, recos = await asyncio.gather(
_safe(c, USR_URL),
_safe(c, ORD_URL),
_safe(c, REC_URL),
)
return Response({
"usuario": usuario or {"nombre": "Usuario"},
"pedidos": pedidos["results"] if pedidos else [],
"recomendaciones": recos["items"] if recos else [],
})
Si recos cae, el dashboard sigue funcionando sin recomendaciones. Lo opuesto a "todo o nada", que en frontend mata UX.
Documentacion separada por BFF
Cada BFF tiene su propio /api/schema/ y /api/docs/. drf-spectacular se configura por proyecto. El equipo movil puede ver solo sus endpoints sin ruido.
Cuando NO usar BFF
- Un solo frontend: si solo tienes una web, no necesitas BFF. Es la propia API.
- Equipo pequeno: mantener N codebases adicionales solo merece la pena con varios equipos frontend independientes.
- Servicios sencillos que ya devuelven exactamente lo que necesita el frontend.
Anti-patrones
- Logica de negocio en el BFF: el BFF agrega y transforma, no calcula precios ni aplica descuentos. Eso vive en los servicios.
- BFF compartido entre web y movil: vuelve al problema original. Cada uno su BFF.
- Cero observability: el BFF es el punto de entrada. Sin trazas, debugging un dashboard lento es ciego. Activa OpenTelemetry desde el dia 1.
- Acoplar al frontend en exceso: si cada cambio de UI implica deploy del BFF, has trasladado el monolito una capa abajo. Mantener tipos genericos suficientes para cubrir las pantallas relacionadas.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Django es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Django
Explora más contenido relacionado con Django y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Diferenciar BFF, API Gateway y monolito API. Entender por que un frontend movil necesita un BFF distinto al web. Implementar un BFF con DRF que orquesta llamadas a 3-4 microservicios. Usar async views + httpx + asyncio.gather para paralelismo. Cachear respuestas BFF por sesion. Compartir autenticacion JWT entre BFFs. Documentar cada BFF de forma independiente. Decidir cuando un BFF justifica su coste operativo.
Cursos que incluyen esta lección
Esta lección forma parte de los siguientes cursos estructurados con rutas de aprendizaje