wandres.dev
NIVEL DIOS: SÍNTESIS · la plataforma completa

Arquitecturas de referencia: cuatro planos completos

Un modelo mental solo se demuestra dibujando sistemas enteros con él. Cuatro arquetipos cubren casi todo lo que se construye sobre esta plataforma: el sitio de contenido, el SaaS multi-tenant, la aplicación en tiempo real y el producto de IA. De cada uno vemos el plano completo, qué pieza carga con el peso, cuál es su cuello de botella característico, en qué se degrada cuando crece y cuál es el error de diseño que lo mata. Y sobre todo, qué tienen en común: los cuatro son el mismo esqueleto con distinta unidad de aislamiento.

⏱ 26 min

Un modelo mental que no dibuja sistemas completos es una colección de opiniones. Esta lección somete el plano de los dos ejes a la prueba dura: cuatro arquitecturas reales, cada una con su reparto de piezas, su cuello de botella y su forma característica de romperse. No son plantillas para copiar, porque ningún producto real coincide con un arquetipo; son referencias contra las que contrastar el tuyo para descubrir en qué se parece, en qué se aparta y si apartarse fue una decisión o un accidente. Verás además algo que solo se aprecia con las cuatro juntas: debajo son la misma arquitectura, y lo único que cambia es qué se toma como unidad de aislamiento.

🎯 Al terminar esta lección sabrás
  • Dibujar el plano completo de cuatro arquetipos y saber qué pieza sostiene cada uno.
  • Identificar el cuello de botella característico de cada arquitectura antes de encontrarlo en producción.
  • Reconocer el error de diseño que arruina cada arquetipo y por qué aparece tarde.
  • Ver el esqueleto común y entender que lo que cambia es la unidad de aislamiento.

Contenido y SaaS: cuando el aislamiento es la URL o el inquilino

El sitio de contenido —documentación, medios, comercio, cualquier cosa donde la mayoría de las peticiones piden lo mismo— es el arquetipo donde el eje del usuario gana sin discusión. Su plano es corto: assets estáticos servidos directamente desde la red, un Worker que solo interviene cuando hay algo que personalizar, la Cache API como primer y casi único almacén, KV para las banderas y la configuración que se leen en cada petición, R2 para los bytes pesados y D1 para el catálogo estructurado que alimenta las páginas. La regla que lo define es que la petición ideal no ejecuta código: si un asset resuelve, el isolate ni se despierta.

// La ruta ideal del arquetipo de contenido: cero viajes cuando acierta.
const cache = caches.default;
const clave = new Request(`${url.origin}${url.pathname}?v=${idioma}-${moneda}`, req);
const cacheada = await cache.match(clave);
if (cacheada) return cacheada;

const respuesta = await construirPagina(env, url);
respuesta.headers.set("cache-control", "public, max-age=60, stale-while-revalidate=600");
ctx.waitUntil(cache.put(clave, respuesta.clone()));
return respuesta;

La directiva que sostiene este arquetipo en producción es stale-while-revalidate, y merece nombrarse porque cambia la naturaleza del compromiso. Sin ella, cada expiración produce una petición lenta que alguien tiene que sufrir; con ella, el usuario recibe la copia antigua al instante mientras la red busca la nueva por detrás, de modo que el desfase deja de pagarse en latencia y pasa a pagarse en frescura, que casi siempre es la moneda más barata.

Su cuello de botella no es el cómputo sino la invalidación. El día que alguien corrige un precio, la pregunta pasa a ser cuántos minutos vive el error en trescientas ubicaciones, y ahí es donde se decide si tu diseño era serio. La respuesta madura no es bajar el tiempo de vida hasta que la caché deje de servir para nada, sino separar la clave de caché por variante —idioma, moneda, segmento— y purgar por etiqueta cuando el contenido cambia, de modo que el desfase sea explícito y acotado en lugar de global y difuso.

El error que mata este arquetipo es la personalización difusa. Empieza con un banner que saluda al usuario por su nombre en la página de inicio; para pintarlo hay que leer la sesión; leer la sesión impide cachear; y de pronto una página que servía trescientos mil accesos desde el punto de presencia los ejecuta todos contra el origen. La solución es vieja y sigue siendo la buena: cachear el esqueleto y traer lo personal aparte, con HTMLRewriter inyectando en el flujo o con una petición secundaria que solo devuelve lo que varía.

Hay una métrica que resume la salud de todo el arquetipo y que conviene tener en un panel desde el primer día: la proporción de aciertos de caché por ruta, no agregada. El agregado miente, porque una portada con noventa y nueve por ciento de aciertos esconde una ruta de producto con cero, y es esa la que está sosteniendo la factura y la latencia. Cuando esa proporción cae en una ruta concreta, casi siempre es porque alguien añadió una variante nueva a la clave de caché sin darse cuenta: una cabecera, una cookie, un parámetro de campaña que el navegador arrastra y que fragmenta el mismo contenido en mil copias distintas.

El SaaS multi-tenant cambia el eje dominante y con él el esqueleto entero. Aquí casi nada se cachea globalmente porque casi todo depende de quién pregunta, y la arquitectura se organiza alrededor de una única obsesión: que un inquilino no vea jamás los datos de otro. El plano típico es un Worker de borde que resuelve el subdominio o la cabecera, valida el token y deriva el identificador de inquilino; un servicio interno alcanzado por service binding que concentra la lógica de negocio; D1 o Hyperdrive según haya base nueva o heredada; R2 con prefijos por inquilino para los ficheros; y KV para el plan y los límites de cada cuenta, que se leen en cada petición y cambian con poca frecuencia.

// El aislamiento no se comprueba en cada consulta: se establece una vez y se transporta.
type Ctx = { tenantId: string; plan: "free" | "pro" };

async function conInquilino(req: Request, env: Env): Promise<Ctx> {
  const claims = await verificarToken(req, env.JWKS);
  if (!claims.tid) throw new Response("sin inquilino", { status: 403 });
  const plan = await env.PLANES.get(claims.tid, "json");
  return { tenantId: claims.tid, plan: plan?.nivel ?? "free" };
}

El cuello de botella del SaaS es el inquilino ruidoso: una cuenta que consume la capacidad compartida y degrada a todas las demás. Y su error característico es más silencioso y más grave: filtrar el aislamiento por olvido. Basta una consulta escrita a mano sin la cláusula del inquilino para que el modelo de seguridad entero se venga abajo, y ese fallo no produce un error, produce datos de más. Por eso la práctica correcta es que ninguna capa de negocio construya SQL suelto: el filtro por inquilino vive en la capa de acceso a datos y no hay forma de escribir una consulta que lo omita.

El SaaS tiene además una decisión estructural que conviene tomar explícitamente y pronto: una base para todos o una base por inquilino. La base compartida es más simple de operar, permite consultas agregadas triviales y concentra el riesgo en el filtro; la base por inquilino —viable en esta plataforma porque crear una D1 es barato— da aislamiento físico, migraciones escalonadas y borrado limpio al darse de baja un cliente, a cambio de que cualquier pregunta que cruce inquilinos se vuelva un ejercicio de agregación. La regla práctica es que si vendes a empresas grandes con exigencias de aislamiento contractual, el coste operativo de la segunda opción es dinero bien gastado; si vendes autoservicio a miles de cuentas pequeñas, no lo es.

⚠️
El aislamiento que se comprueba en cada sitio no se comprueba en ninguno

Un modelo multi-tenant sano tiene exactamente un punto donde se decide el inquilino y un punto donde se aplica. Si tu código repite la comprobación en cuarenta funciones, la pregunta ya no es si alguna se olvidará, sino cuándo, y el olvido no se manifiesta como excepción sino como una fila de otro cliente en una respuesta. Concentra la derivación de identidad en el borde, pásala como contexto tipado y haz que la capa de datos sea la única que la traduzca a filtros.

Tiempo real: cuando el aislamiento es la sala

La aplicación en tiempo real —chat, colaboración, presencia, juegos, subastas— es el arquetipo donde el eje se colapsa: la unidad de diseño ya no es la petición sino la sala, y la sala es un Durable Object. Los clientes abren un WebSocket contra el Worker, que lo entrega al objeto correspondiente mediante idFromName con el identificador de la sala. Ese objeto mantiene las conexiones, guarda el estado con hibernación para no pagar memoria mientras nadie habla, difunde los mensajes a los conectados, persiste en su SQLite lo que debe sobrevivir y usa alarmas para lo que ocurre por tiempo: expulsar inactivos, cerrar la subasta, compactar el historial.

flowchart TD
U1[cliente A] --> W[Worker de borde]
U2[cliente B] --> W
W --> ID[idFromName con el identificador de sala]
ID --> DO[Durable Object de la sala]
DO --> WS[conexiones con hibernacion]
DO --> SQ[SQLite del objeto]
DO --> AL[alarma para expiracion y cierre]
DO --> Q[Queue para trabajo pesado fuera de la sala]
Q --> D1[D1 historico consultable]

Su cuello de botella es la granularidad. Un objeto ejecuta de uno en uno, así que la sala es el límite duro de concurrencia: si eliges una sala por organización en lugar de una por conversación, todos los mensajes de la empresa se serializan en un hilo. Y su error característico es meter trabajo pesado dentro del objeto —generar un PDF, llamar a un modelo, recalcular un ranking— porque mientras eso ocurre nadie más habla en esa sala. La disciplina es que el objeto coordine y delegue: lo que no es coordinación sale por una cola.

Hay una segunda decisión que este arquetipo obliga a tomar pronto y que casi siempre se aplaza: qué parte del estado vive en la sala y qué parte vive fuera. La tentación es guardarlo todo en el SQLite del objeto, porque es cómodo y transaccional, pero un histórico que crece sin fin dentro de una sala la vuelve lenta de despertar y la hace imposible de consultar desde otro sitio. El reparto que funciona es que la sala guarde solo lo que hace falta para atender a los conectados ahora —participantes, últimos mensajes, estado de la partida— y que todo lo que sea histórico consultable salga por una cola hacia D1, donde vive en un formato que admite preguntas.

Un detalle económico que decide la viabilidad del arquetipo es la hibernación. Sin ella, mantener diez mil salas abiertas significa pagar diez mil objetos en memoria aunque nadie hable en el noventa y nueve por ciento de ellas. Con hibernación, el objeto libera memoria mientras las conexiones siguen abiertas y despierta cuando llega un mensaje, de modo que el coste sigue a la actividad y no al número de conexiones. Diseñar una aplicación de presencia sin contar con eso es la diferencia entre un producto sostenible y uno que solo funciona en la demostración.

El producto de IA: cuando el aislamiento es el presupuesto

El producto de IA es el arquetipo más reciente y el que peor tolera copiarse de los otros tres, porque su restricción dominante no es la latencia ni la concurrencia, es el coste por petición. El plano suele tener cuatro capas: la ingesta, que trocea documentos, calcula embeddings con Workers AI y los guarda en Vectorize con metadata para filtrar; la recuperación, que convierte la pregunta en vector y trae los fragmentos relevantes; la generación, que arma el prompt con contexto y citas y responde en streaming; y una capa transversal de gobierno que pasa por AI Gateway para cachear respuestas, limitar el ritmo, reintentar, hacer fallback a otro proveedor y —sobre todo— contar lo que se gasta.

// El presupuesto no es una alerta a posteriori: es una condicion previa a la llamada.
const gasto = await env.PRESUPUESTO.get(`org:${tenantId}`, "json");
if (gasto.acumulado >= gasto.techoMensual) {
  return Response.json({ error: "presupuesto agotado" }, { status: 429 });
}
const salida = await env.AI.run(modelo, entrada, { gateway: { id: "principal" } });
ctx.waitUntil(env.CONTABILIDAD.send({ tenantId, modelo, tokens: salida.usage }));

La ingesta y la consulta tienen ritmos tan distintos que conviene tratarlas como dos sistemas separados aunque compartan índice. La ingesta es un proceso por lotes, tolerante a la latencia, que se puede reintentar y que encaja de forma natural en un Workflow: trocear, incrustar, escribir en Vectorize, marcar el documento como indexado. La consulta es interactiva, sensible a cada centenar de milisegundos y no admite reintentos silenciosos porque el usuario está mirando. Mezclar ambas en el mismo camino de código es la causa habitual de que una reindexación masiva degrade la experiencia de todos.

Arquetipo Pieza que carga el peso Cuello de botella Error que lo mata
Sitio de contenido Cache API y assets Invalidación Personalización difusa
SaaS multi-tenant D1 o Hyperdrive Inquilino ruidoso Filtro de inquilino olvidado
Tiempo real Durable Objects Granularidad de la sala Trabajo pesado dentro del objeto
Producto de IA Workers AI y Vectorize Coste por petición Ausencia de presupuesto

El error que arruina un producto de IA no es técnico sino económico: desplegar sin presupuesto. Los otros tres arquetipos degradan cuando fallan —la página va lenta, la sala se atasca—, pero un sistema de IA sin límites no degrada: factura. Un bucle de agente que no converge, un usuario que descubre cómo pedir resúmenes de documentos enormes o una integración que reintenta sin techo pueden consumir en una noche el presupuesto de un trimestre sin disparar ninguna alarma de rendimiento, porque desde fuera todo parece funcionar perfectamente.

De ahí se deriva la consecuencia que da nombre a este arquetipo: su unidad de aislamiento no es un recurso ni una entidad, es una cuenta de gasto. Igual que en un SaaS ninguna consulta debe poder escribirse sin filtro de inquilino, aquí ninguna llamada a un modelo debe poder escribirse sin pasar por el punto que comprueba el techo y contabiliza el consumo. Cuando esa disciplina existe, el límite se fija por organización, por usuario y por sesión, y la degradación es explícita —un mensaje honesto de presupuesto agotado— en lugar de silenciosa y facturada.

💡
Los cuatro arquetipos se mezclan y eso es normal

Ningún producto real es puro. Una documentación con buscador semántico es contenido más IA. Un SaaS con edición colaborativa es multi-tenant más tiempo real. Un comercio con recomendaciones es contenido más IA más una pizca de coordinación en el stock. La utilidad de los arquetipos no está en clasificarte en uno, sino en que puedas decir de cada ruta a cuál se parece, y por tanto qué cuello de botella esperar y qué error vigilar en esa ruta concreta.

El esqueleto común

Puestas una al lado de otra, las cuatro arquitecturas revelan que comparten estructura. Todas tienen un borde que recibe y resuelve identidad; una capa de decisión que traduce esa identidad en permisos y límites; un almacén caliente para lo que se lee mucho y cambia poco; un almacén frío o estructurado para la verdad; un mecanismo de coordinación para lo que no puede ocurrir a la vez; y una vía asíncrona para todo lo que el usuario no debe esperar. Lo único que varía entre los cuatro es cuál es la unidad de aislamiento: la URL en el contenido, el inquilino en el SaaS, la sala en el tiempo real y el presupuesto en la IA.

Esa observación tiene una consecuencia práctica fuerte. Cuando empiezas un sistema nuevo, la primera pregunta no es qué base de datos usar ni si conviene un framework: es cuál es mi unidad de aislamiento, porque de ella se derivan casi todas las demás decisiones. Si la unidad es la URL, tu trabajo es maximizar aciertos de caché. Si es el inquilino, tu trabajo es que sea imposible escribir una consulta sin filtro. Si es la sala, tu trabajo es elegir su granularidad. Si es el presupuesto, tu trabajo es que cada llamada tenga techo y cada techo tenga alarma.

Ese esqueleto también explica por qué los cuatro se despliegan de la misma manera, y eso no es un detalle menor. Un Worker de borde, uno o dos servicios internos alcanzados por RPC, un puñado de bindings declarados en el manifiesto y nada más: sin redes privadas que configurar, sin credenciales que rotar entre servicios, sin balanceadores. La topología del despliegue se lee entera en el tipo del entorno, que es la forma más honesta de documentar una arquitectura porque no puede quedarse obsoleta sin que el compilador se queje.

// El esqueleto comun, expresado como tipo: cada campo es una decision ya tomada.
interface Env {
  ASSETS: Fetcher;                    // el borde que no ejecuta codigo
  NEGOCIO: Service<Negocio>;          // la capa que decide, por RPC
  CONFIG: KVNamespace;                // caliente: mucho leido, poco cambiado
  DB: D1Database;                     // la verdad estructurada
  SALAS: DurableObjectNamespace;      // coordinacion por entidad
  EVENTOS: Queue<Evento>;             // lo que el usuario no espera
}

Conviene cerrar con una advertencia sobre el uso de estos planos, porque el modo natural de leerlos es el equivocado. No son puntos de partida: ningún sistema serio se construye eligiendo un arquetipo el primer día y rellenando sus cajas. Son instrumentos de contraste, y su momento de máxima utilidad no es el arranque sino la revisión, cuando ya hay código, ya hay tráfico y la pregunta relevante es por qué esta ruta se parece a un arquetipo distinto del resto del sistema. Esa pregunta casi siempre tiene una respuesta interesante, y a veces revela que lo que empezó como un sitio de contenido lleva meses siendo un SaaS sin que nadie lo haya dicho en voz alta.

🗂️

Cuatro planos, un esqueleto

Borde que identifica, capa que decide, almacén caliente, almacén de verdad, coordinación y vía asíncrona. Lo que cambia es la unidad de aislamiento.

⚠️

Cada arquetipo muere de lo suyo

Invalidación, filtro olvidado, granularidad y presupuesto. Conocer el fallo característico permite vigilarlo antes de que ocurra.

🔱

Los sistemas reales se mezclan

Nadie encaja en un arquetipo puro. La habilidad no es clasificarse, es saber a cuál se parece cada ruta y por tanto qué esperar de ella.

Una arquitectura de referencia no es un diagrama para copiar, es un conjunto de fallos anticipados

El valor de estos cuatro planos no está en las cajas ni en las flechas, y quien los memorice como diagramas se habrá llevado la parte inútil. Está en que cada uno viene acompañado de un modo de fallo que se conoce de antemano y que aparece siempre en el mismo momento del ciclo de vida del producto: tarde, cuando ya hay usuarios, cuando el código que lo causa lleva meses funcionando y cuando el cambio que lo arregla es estructural. La personalización que rompe la caché no se nota el día que se añade, se nota el mes que el tráfico se multiplica; el filtro de inquilino olvidado no da error, da datos ajenos y puede tardar un año en descubrirse; una sala demasiado gruesa funciona perfecto con veinte usuarios y se atasca con dos mil; y un sistema de IA sin presupuesto es indistinguible de uno sano hasta que llega la factura. Ese patrón —fallos que no se manifiestan como fallos y que llegan cuando ya son caros de arreglar— es exactamente por qué las arquitecturas de referencia existen. No sirven para construir, porque construir se aprende construyendo; sirven para hacerte una pregunta antes de tiempo, que es la única forma de hacerla barata. Y hay una segunda capa, más profunda, que solo se ve mirando los cuatro juntos: la unidad de aislamiento es la decisión de la que cuelga todo lo demás, y sin embargo casi nunca se toma explícitamente. Se hereda del tutorial que se siguió, del framework que se eligió o del primer caso de uso que se implementó, y después el sistema entero se organiza alrededor de una decisión que nadie recuerda haber tomado. Nombrarla en voz alta el primer día —esta aplicación aísla por inquilino, esta por sala, esta por presupuesto— es probablemente el acto de diseño más rentable de todo este track, porque cuesta cinco minutos y determina, sin exagerar, la forma de todo lo que se construirá encima durante los años siguientes.

⚔️ Contrasta tu sistema con los cuatro planos
  1. Nombra en una frase cuál es la unidad de aislamiento de un proyecto que conozcas y qué decisiones se derivan de ella.
  2. Elige el arquetipo al que más se parece su ruta principal y di qué cuello de botella deberías estar vigilando ya.
  3. Encuentra en él una ruta que pertenezca a un arquetipo distinto del resto y explica qué implica esa mezcla.
  4. Describe cómo cachearías una página personalizada sin sacrificar los aciertos de caché del esqueleto.
  5. Argumenta por qué el fallo característico de cada arquetipo aparece tarde y qué señal temprana podrías instrumentar.