BFF Multi-Implementación: Comparativa de Paradigmas Backend for Frontend
1. Introducción
Este proyecto es una suite de comparación multi-implementación del patrón BFF (Backend For Frontend). Su objetivo es mostrar las características, ventajas y trade-offs de cinco tecnologías y paradigmas de desarrollo backend distintos, exponiendo exactamente la misma funcionalidad a través de cada uno de ellos.
Todo el desarrollo se ha realizado íntegramente utilizando Claude Code, como prueba de concepto para la generación de proyectos completos mediante prompts. Es también una demostración práctica de hasta dónde puede llegar un LLM en dominios técnicos donde el autor —reconocidamente— no tiene experiencia directa en todos los stacks (Quarkus, Apache Camel).
El código fuente está disponible en GitHub: iCesofT/bff-samples
2. Objetivo
Comparar el comportamiento, rendimiento y consumo de recursos de cinco implementaciones equivalentes de un BFF que:
Consulta la API pública REST Countries para obtener datos geográficos de un país.
Consulta la API pública Open-Meteo para obtener el tiempo meteorológico actual de la capital.
Agrega ambas respuestas en un único endpoint REST unificado.
Todas las implementaciones siguen Arquitectura Hexagonal (Ports & Adapters) y exponen el mismo contrato de API.
3. Las cinco implementaciones
| Módulo | Framework | Lenguaje | Servidor | Modelo de ejecución | Puerto |
|---|---|---|---|---|---|
| Spring Boot 4 | Java 25 | Tomcat | Bloqueante (thread-per-request) | 8081 |
| Spring Boot 4 | Java 25 | Netty | Reactivo (Mono/Flux) | 8082 |
| Quarkus 3 | Java 25 | Vert.x | Reactivo + GraalVM nativo | 8083 |
| Apache Camel 4 + Spring Boot 4 | Java 25 | Tomcat | Bloqueante con EIP routes | 8084 |
| Fastify 5 | JavaScript (Node 22) | Fastify | Event loop asíncrono | 3000 |
4. Arquitectura Hexagonal
Todas las implementaciones siguen la misma estructura interna, garantizando que la lógica de negocio permanece aislada del framework y de las APIs externas.
domain/
├── model/ # CountryInfo, WeatherInfo, etc.
├── port/ # Interfaces CountryPort, WeatherPort
└── exception/ # Excepciones de dominio
application/
└── GetWeatherByCountryUseCase # Lógica de negocio pura
infrastructure/
├── api/ # Controladores / rutas HTTP (adaptador primario)
├── config/ # Configuración, clientes HTTP, caché
├── restcountries/ # Adaptador REST Countries API (adaptador secundario)
└── openmeteo/ # Adaptador Open-Meteo API (adaptador secundario)El único caso de uso de negocio es GetWeatherByCountryUseCase, que orquesta la llamada a CountryPort y WeatherPort sin conocer ningún detalle de infraestructura.
5. API expuesta
Todos los módulos exponen el mismo endpoint:
GET /api/v1/weather?country={nombre_del_pais}5.1. Ejemplo de respuesta
{
"country": {
"commonName": "Spain",
"officialName": "Kingdom of Spain",
"cca2": "ES",
"cca3": "ESP",
"ccn3": "724",
"latitude": 40.0,
"longitude": -4.0
},
"capital": {
"name": "Madrid",
"latitude": 40.4165,
"longitude": -3.7026
},
"weather": {
"temperature": 22.5,
"temperatureUnit": "°C",
"windSpeed": 14.2,
"windSpeedUnit": "km/h"
}
}5.2. Errores (RFC 9457 Problem Detail)
Los errores siguen el estándar RFC 9457 Problem Detail en todas las implementaciones:
| Código | Situación |
|---|---|
| Parámetro |
| País no encontrado (la API de países devuelve 404) |
| API externa no disponible (circuit breaker abierto) |
| Error inesperado |
6. Características técnicas comparadas
6.1. Resiliencia
| Módulo | Librería y estrategia |
|---|---|
| Resilience4j — |
| Resilience4j Reactor — operadores aplicados sobre |
| SmallRye Fault Tolerance — |
| Camel Resilience4j Policy — circuit breaker declarado en la ruta EIP |
| Opossum 8 — circuit breaker + retry manual (3 intentos, excluye 404) |
| El circuit breaker no contabiliza los 404 como fallos: un país no encontrado es una respuesta válida del servicio externo, no un fallo de infraestructura. |
6.2. Caché en dos niveles
Todos los módulos implementan caché en dos niveles:
L1 (local) — Caffeine / Quarkus Cache / node-cache: caché en memoria sin latencia de red. TTL países: 1 h; TTL tiempo: 15 min.
L2 (distribuida) — Redis 7: caché compartida entre instancias. Solo para datos de países (TTL 1 día), ya que el TTL del tiempo meteorológico es demasiado corto para justificar la latencia de red adicional.
7. Estimación de recursos
| Módulo | RAM aproximada | Tiempo de arranque |
|---|---|---|
| ~300 MB | ~3 s |
| ~200 MB | ~2 s |
| ~80 MB | <1 s |
| ~50 MB | <50 ms |
| ~350 MB | ~4 s |
| ~60 MB | <1 s |
Los datos anteriores son estimaciones en condiciones de carga moderada. Los valores definitivos se obtienen ejecutando las pruebas de rendimiento con k6 descritas más adelante.
8. Infraestructura
8.1. API Gateway (Nginx)
Nginx actúa como puerta de entrada unificada en el puerto 8080, enrutando el tráfico por prefijo de ruta y reescribiendo la URL antes del proxy:
| Prefijo | Destino |
|---|---|
| Spring MVC — |
| Spring WebFlux — |
| Quarkus — |
| Apache Camel — |
| Node.js / Fastify — |
| Estado del gateway (respuesta local 200 OK) |
Configuración adicional: keepalive 16 conexiones por upstream, gzip habilitado para application/json, logs con upstream_response_time.
8.2. Docker Compose
El docker-compose.yml de la raíz orquesta los cinco servicios más Redis y Nginx, con healthchecks y depends_on para garantizar que Redis esté listo antes de que arranquen los BFF.
| Servicio | Límite de RAM |
|---|---|
redis | 32 MB |
nginx | 16 MB |
bff-nodejs | 64 MB |
bff-quarkus | 256 MB |
bff-webmvc | 512 MB |
bff-webflux | 512 MB |
bff-camel | 512 MB |
Todos los Dockerfiles son multi-stage: etapa de compilación, extracción de capas JAR (solo Spring Boot) y runtime sobre imagen Alpine. Los contenedores Java usan -XX:+UseContainerSupport -XX:+UseZGC -XX:MaxRAMPercentage=75.0 y arrancan como usuario no-root.
9. Cómo ejecutar
9.1. Arrancar todo el stack
git clone https://github.com/iCesofT/bff-samples.git
cd bff-samples
docker compose up --build -d9.2. Probar el endpoint
# A través del API Gateway (Nginx puerto 8080)
curl "http://localhost:8080/nodejs/api/v1/weather?country=Spain"
curl "http://localhost:8080/webmvc/api/v1/weather?country=Germany"
curl "http://localhost:8080/webflux/api/v1/weather?country=Japan"
curl "http://localhost:8080/quarkus/api/v1/weather?country=France"
curl "http://localhost:8080/camel/api/v1/weather?country=Italy"10. Pruebas de rendimiento con k6
El directorio k6/ contiene un test de carga que ejercita el endpoint en todos los módulos bajo diferentes niveles de concurrencia con 30 países europeos, americanos y asiáticos aleatorizados por VU.
11. Compilación nativa con Quarkus
El módulo bff-quarkus soporta compilación nativa con GraalVM/Mandrel, produciendo un binario que arranca en menos de 50 ms y consume ~50 MB de RAM:
cd bff-quarkus
./mvnw package -Pnative12. Variables de entorno
Todas las implementaciones leen las mismas variables de entorno, con valores por defecto razonables:
REST_COUNTRIES_URL=https://restcountries.com/v3.1
OPEN_METEO_URL=https://api.open-meteo.com/v1
REDIS_HOST=redis # 'localhost' fuera de Docker
REDIS_PORT=6379
# Circuit Breaker
CB_TIMEOUT_MS=5000
CB_ERROR_THRESHOLD_PERCENT=50
CB_RESET_TIMEOUT_MS=10000
# Cache TTLs
CACHE_COUNTRIES_TTL_SECONDS=3600
CACHE_WEATHER_TTL_SECONDS=90013. Reflexiones sobre el desarrollo con Claude Code
Este proyecto nació como una prueba de concepto: ¿puede un LLM generar un proyecto completo, multi-módulo y con decisiones técnicas coherentes en stacks que el autor no domina?
La respuesta es que sí, con matices. Claude Code fue capaz de:
Diseñar y mantener la consistencia de la Arquitectura Hexagonal en los cinco módulos.
Tomar decisiones de diseño fundamentadas (por ejemplo, no cachear el tiempo en Redis porque el TTL corto no justifica la latencia de red).
Generar Dockerfiles multi-stage, configuración de Nginx, rutas Camel EIP y gestión reactiva con Resilience4j sin intervención manual.
Respetar restricciones explícitas: sin TypeScript en Node.js, sin comentarios innecesarios, RFC 9457 en todos los errores.
Los puntos donde la supervisión humana sigue siendo imprescindible son la validación funcional real del comportamiento bajo carga y la detección de sutilezas en stacks menos convencionales (Camel EIP, compilación nativa de Quarkus).
14. Requisitos
Docker y Docker Compose — para levantar todo el stack de una vez.
Java 25 — para compilar los módulos JVM individualmente (fuera de Docker).
Node.js 22 LTS — para el módulo Node.js individualmente.
GraalVM / Mandrel — solo para la compilación nativa de Quarkus.
k6 — para las pruebas de rendimiento.
15. Código fuente
El repositorio completo está disponible en github.com/iCesofT/bff-samples.