Por qué un BFF y no queries directas
Cuadrante 04_EXPLANATION — el porqué, no el cómo. Si vienes a resolver una tarea, ve a 02_HOW_TO/.
La tentación
El panel muestra clientes de Gastro y de Travel. Las tres bases viven en el mismo Postgres. Un
SELECT con dos conexiones y listo: menos código, menos latencia, menos piezas.
Y sin embargo Operator habla con las verticales solo por HTTP, con un token, pidiéndoles a ellas que hagan el trabajo. Vale la pena saber por qué, porque el atajo va a volver a parecer buena idea.
Razón 1: el esquema de otro no es una API
Si el panel hiciera SELECT plan_type FROM gastro_tenants, esa columna quedaría congelada.
Renombrarla en Gastro —una refactorización interna, de su propio dominio— rompería un panel que
vive en otra carpeta, con otro CI, que su autor quizá ni recuerda. El acoplamiento no se ve en
ningún import: aparece en producción, un martes.
Con HTTP, el contrato es explícito y versionado. Gastro puede renombrar lo que quiera mientras
/platform/tenants siga respondiendo lo mismo.
Razón 2: las reglas de negocio no se pueden copiar
Suspender un tenant de Gastro no es UPDATE gastro_tenants SET is_active = false. Es eso, más
invalidar el caché de su carta pública, más escribir la auditoría, más lo que Gastro decida
mañana que también hace falta.
Un UPDATE desde el panel produciría un tenant a medio suspender: la base dice una cosa, el
caché sirve otra. Y nadie se entera hasta que un comensal escanea el QR de un restaurante que
"ya no está".
Travel es todavía más claro. Su ciclo de cobro tiene dunning: un job que mira estados y fechas
para decidir a quién recordarle, a quién marcar en mora y a quién expirar. Mover
trial_ends_at con un UPDATE a espaldas de esa lógica es cómo se le manda un correo de cobro
a un cliente al que le acabas de regalar una extensión. Por eso las mutaciones de Travel pasan
por las primitivas de su dominio de billing: no para respetar una capa por gusto, sino porque
esa capa sabe cosas que el panel no.
Razón 3: la auditoría tiene que ser atómica, y solo la dueña puede
La bitácora es el punto entero del panel: quién cambió qué y por qué. Si el panel escribiera el
cambio en gastro_db y el audit en otra transacción, cualquier fallo entre las dos deja un
cambio sin rastro — precisamente el agujero que veníamos a tapar.
La vertical dueña escribe ambas cosas en una transacción. Un cambio sin su fila de bitácora no existe.
Razón 4: el navegador nunca debe poder hacer esto
El token que abre /platform/* de Gastro vive en el servidor del BFF. El navegador solo tiene
un JWT de operador, que fuera de ops.timeliber.com.co no vale nada.
Si el panel fuera un frontend hablando directo con las verticales, ese token tendría que estar en el bundle — es decir, público. No hay forma de esconder un secreto en JavaScript que se descarga.
Qué cuesta
Honestamente:
- Latencia: cada pantalla es un salto HTTP extra. Se paga con timeout (10s) y degradación: si una vertical no responde, el panel dice que está caída en vez de colgarse con ella.
- Duplicación aparente:
CustomerCardse parece aTenantCardde Gastro. No es duplicación: es traducción.TenantCardhabla el idioma de Gastro;CustomerCard, el del panel. Que hoy se parezcan es coincidencia, no una razón para fusionarlos. - Un dominio
platform/por vertical: código nuevo en cada una. A cambio, cada vertical decide qué expone y qué no.
La regla, en una línea
El panel orquesta y traduce. La vertical decide y ejecuta. Si te encuentras escribiendo lógica de negocio en el BFF —qué plan puede pasar a cuál, cuántos días de prueba son válidos— estás en el archivo equivocado: eso va en la vertical dueña, y el BFF solo propaga su respuesta.