La pregunta no era "qué es lo más moderno", era "qué podemos operar sin un tercero de guardia"

Assilek es un SaaS de galerías fotográficas para que estudios de fotografía compartan pruebas con sus clientes finales: galerías privadas con marca de agua configurable, selección de favoritas, comentarios, paquetes con límites y expiración. Lo construimos Edel Moreno Lemus, con formación en ingeniería informática, y Noel Moreno Lemus, PhD en Data Science — ambos con empleo de tiempo completo en otro lado. Ese detalle no es anecdótico, es la variable que más pesó en cada decisión de arquitectura. Un equipo de dos personas sin dedicación exclusiva no puede sostener una guardia de infraestructura de 24/7, no puede permitirse que un cluster mal configurado se caiga un martes a las 3am y nadie lo vea hasta la mañana, y no tiene tiempo para aprender a operar una pieza de infraestructura solo porque es la que usan las empresas grandes.

Eso descarta opciones antes de evaluarlas por mérito técnico. No es que Kubernetes sea mala tecnología — es que Kubernetes bien operado necesita a alguien pensando en el cluster de forma recurrente, y esa persona no existe en este equipo. La pregunta que terminó ordenando cada decisión no fue "qué arquitectura es la más escalable" sino "qué arquitectura sigue funcionando correctamente si nadie la mira durante una semana".

Cloud Run como base: contenedores sin cluster que mantener

Todo el backend de Assilek —el API principal en Spring Boot y el servicio de procesamiento de imágenes— corre en Google Cloud Run, dentro del proyecto photographers-488601, región us-central1. Cloud Run toma un contenedor y lo ejecuta como servicio HTTP administrado: escala automáticamente entre cero y N instancias según el tráfico entrante, cobra por tiempo de CPU realmente consumido y no expone ni un nodo, ni un scheduler, ni un control plane que alguien tenga que parchear. La unidad de despliegue sigue siendo la misma que en cualquier plataforma basada en contenedores —una imagen Docker— pero desaparece la capa de operar el cluster que la ejecuta.

Para un equipo de dos personas eso importa más de lo que suena. La alternativa habitual en este tipo de arquitectura —un cluster de Kubernetes administrado— sigue exigiendo decisiones activas: dimensionar node pools, gestionar actualizaciones de versión, configurar autoscaling a nivel de nodo además del de pod, vigilar el uso de recursos del cluster completo y no solo de cada servicio. Ese trabajo no es imposible para un equipo chico, pero es trabajo que compite directamente con construir producto. Cloud Run elimina esa capa completa a cambio de menos control fino sobre el entorno de ejecución — un cambio que, para el tamaño de tráfico actual de Assilek (en fase de captación inicial de clientes, sin picos de carga sostenidos), es una simplificación real y no una limitación que nos esté costando algo.

Por qué el procesamiento de imágenes vive en su propio servicio

El backend de Assilek no es un monolito único. Además del API principal (Next.js 14 con App Router en el frontend, Spring Boot 3 sobre Java 21 en el backend), existe un segundo servicio —image-service, también en Spring Boot— dedicado exclusivamente a procesar imágenes: aplicar marcas de agua, generar variantes y thumbnails. Ese servicio no recibe tráfico HTTP directo de los usuarios; consume mensajes de Pub/Sub.

La razón de separarlo no es estética. Procesar una imagen —decodificarla, componer una marca de agua sobre ella según la configuración de la organización, re-codificarla, subir el resultado a almacenamiento— es trabajo de CPU y memoria intensivo y de duración variable: una imagen puede tardar mucho más que otra según su tamaño original. Si esa lógica viviera dentro del mismo servicio que atiende las peticiones normales del API (listar galerías, autenticar usuarios, servir la app), un pico de subidas de fotos degradaría la latencia de todo lo demás, incluyendo operaciones que no tienen nada que ver con procesar imágenes. Separarlo en un servicio propio, activado de forma asíncrona por un mensaje en una cola, aísla ese trabajo pesado del resto: el API responde rápido porque nunca espera a que termine el procesamiento, y el image-service puede escalar sus propias instancias de Cloud Run de forma independiente cuando hay más imágenes en cola, sin que eso afecte al servicio principal.

El patrón es simple de nombrar pero fácil de saltarse cuando se está construyendo rápido con dos personas: cualquier operación cuya duración no se puede predecir con precisión no debería vivir en el mismo proceso que responde peticiones síncronas. Se desacopla con una cola, no con un try/catch y una esperanza.

Multi-tenancy resuelta a nivel de organización, no de código

Assilek es multi-tenant: cada estudio de fotografía es una organización, con sus propias galerías, sus propios clientes finales y su propia configuración. La forma en que eso se resuelve técnicamente no es duplicar infraestructura por cliente —eso no escala con dos personas manteniéndola— sino aislar los datos por organización dentro de la misma base de datos (Postgres en Cloud SQL) y hacer que las decisiones específicas de cada tenant se resuelvan como configuración, no como ramas de código.

El ejemplo más concreto es la marca de agua. Cada organización puede configurar su propia marca —texto o logo, centrada o repetida en patrón diagonal, con opacidad, posición y ángulo ajustables— y esa configuración vive a nivel de Organization Settings. Las galerías individuales heredan esos valores por defecto; el fotógrafo solo necesita configurar la marca una vez para que aplique a todo lo que suba después, y puede sobreescribirla puntualmente si una galería específica lo requiere. El código que aplica la marca de agua en image-service es el mismo para las 100% de las organizaciones — lo que cambia es la fila de configuración que lee antes de procesar la imagen, no la lógica que la procesa. Esa distinción —comportamiento configurable vs. comportamiento condicionado por código— es la que permite agregar clientes nuevos sin que cada uno requiera una revisión de código para acomodarlo.

Terraform: no porque el equipo sea grande, sino porque es chico

La infraestructura de Assilek está declarada en Terraform, en infrastructure/terraform/ dentro del repo. Es tentador pensar que Infrastructure as Code es una herramienta para equipos grandes con múltiples personas tocando la nube al mismo tiempo, donde hace falta coordinar cambios. Para un equipo de dos, la razón es la opuesta: cuando la infraestructura se toca con poca frecuencia —semanas o meses entre un cambio y el siguiente— la memoria humana de "por qué está configurado así" se pierde rápido. Sin Terraform, la única fuente de verdad de qué existe y cómo está configurado sería la consola de GCP en un momento dado, sin historial, sin revisión y sin forma de saber si un valor se cambió a mano hace tres meses y nadie más se enteró.

Con la infraestructura declarada como código, un cambio de configuración pasa por el mismo flujo que un cambio de producto: se edita, se revisa el diff, y el estado resultante queda documentado en el historial de git con quién lo hizo y cuándo. Para dos personas que no están mirando la infraestructura todos los días, esa trazabilidad no es un lujo de proceso — es lo que evita que un ajuste hecho apurado un domingo se convierta en una sorpresa sin explicación seis semanas después, cuando ya nadie recuerda haberlo tocado.

Un ejemplo de cómo la infraestructura habilita el negocio, no al revés

Assilek cobra por suscripción vía Stripe Checkout, con Adaptive Pricing multi-moneda activo: el precio que ve un visitante se localiza automáticamente según su país, sin que el equipo tenga que mantener tablas de precios por región a mano. Esto no es una decisión de infraestructura en sí, pero depende de una: que el checkout esté desacoplado del resto del sistema de facturación y de que Stripe sea la única fuente de verdad de qué plan y qué moneda corresponden a cada organización, en vez de que Assilek intente replicar esa lógica internamente. Delegar en el proveedor lo que el proveedor ya resuelve mejor que un equipo de dos personas —conversión de moneda, impuestos, medios de pago regionales— es la misma filosofía que Cloud Run frente a Kubernetes o Pub/Sub frente a un worker propio: construir la lógica de negocio que sí es específica de Assilek, y apoyarse en infraestructura administrada para todo lo que no lo es.

Lo que deliberadamente no hicimos

No fragmentamos el backend en más de dos servicios. Fue tentador, en algún momento, separar más piezas —autenticación, notificaciones, facturación— en servicios independientes siguiendo el patrón de microservicios "correcto" según cualquier charla de arquitectura. No lo hicimos porque cada servicio adicional es una superficie más de despliegue, de logs y de fallos que dos personas sin dedicación exclusiva tienen que vigilar. La única división que se justificó fue la que resolvía un problema real y medible: aislar trabajo de duración impredecible (procesar imágenes) del que responde peticiones síncronas. Todo lo demás vive en el API principal hasta que exista una razón concreta —no estética— para separarlo.

Tampoco montamos multi-región ni alta disponibilidad activa-activa. Cloud Run en una sola región, con el nivel de tráfico actual de Assilek, no ha sido un cuello de botella real. Diseñar para una escala que todavía no existe es tiempo que no se recupera — se puede agregar cuando el tráfico lo exija, no antes.

Cierre

Ninguna de estas decisiones es única ni particularmente sofisticada por separado — Cloud Run, Pub/Sub, Terraform y multi-tenancy por configuración son patrones bien documentados, no una invención de Lemus Digital. Lo que sí es una decisión deliberada es aplicarlos con el criterio correcto para el tamaño real del equipo que los va a operar, en vez de copiar la arquitectura de una empresa con un equipo de plataforma dedicado porque "así se hace". La pregunta que mejor filtra cada elección de infraestructura sigue siendo la misma: si nadie la mira durante una semana, ¿sigue funcionando? Para Assilek, hoy, la respuesta es sí — y esa respuesta es más valiosa para un equipo de dos que cualquier arquitectura más elegante que exigiera lo contrario.