Cómo convertimos el backend único de Omnilude en un MSA con ayuda de la IA
Introducción
Este artículo continúa Así estamos construyendo el backend para Omnilude. En el texto anterior presenté la estructura actual del backend de Omnilude y sus límites de responsabilidad como si fuera un único backend de producto. En este artículo quiero contar cómo esa estructura empezó a separarse, en la práctica, desde un solo proyecto grande hacia varios límites de servicio.
Las conversaciones sobre MSA suelen sonar demasiado abstractas. Decir que un sistema se dividió, que ya se puede desplegar de forma independiente o que se definieron límites es fácil. Lo difícil es visualizar qué cambió de verdad dentro de un proyecto vivo.
El backend de Omnilude tiene material suficiente para explicar eso con claridad. Hoy está dividido en nombres como auth-service, backbone-service, storage-service, ai-service, game-service, blog-service y legacy-service, pero no empezó así. Al principio era un solo backend grande en Spring Boot.
En este artículo voy a explicar por qué ese proyecto único empezó a dividirse, por qué jspring(common) salió primero, por qué legacy-service se dejó a propósito y qué papel jugó realmente la IA en esta transición.
Al principio solo había un backend grande
Antes de la transición, el backend de Omnilude era, dicho de forma simple, un lugar donde todo estaba acumulado dentro del mismo proyecto. Ahí convivían autenticación, IA, lógica de juego, almacenamiento de archivos, eventos en tiempo real y utilidades compartidas.
Ese tipo de estructura se siente cómoda al comienzo. Es rápido agregar funciones y todo se puede tocar dentro del mismo código. Pero a medida que el proyecto crece, empiezan a aparecer los problemas.
- Áreas como autenticación, que requieren mucho cuidado
- Áreas como IA, que cambian seguido y piden experimentación
- Áreas como eventos en tiempo real, donde la estabilidad importa mucho
- Áreas como juegos, donde la lógica de dominio crece rápido
Todas terminan recibiendo la misma presión de despliegue.
Una forma simple de imaginarlo es pensar en un clóset donde guardas ropa de temporada, ropa deportiva, trajes, mantas y una caja de herramientas al mismo tiempo. Al principio parece práctico. Después, cambiar una sola cosa ya se vuelve pesado.
¿Por qué dividirlo?
Yo no me movía con la MSA como objetivo en sí mismo desde el principio. Los motivos eran mucho más prácticos.
El más importante era proteger de los despliegues normales a las funciones que necesitan mantener conexiones largas. En Omnilude había áreas muy cercanas a WebSocket, SSE, flujos de eventos y trabajos asíncronos largos, especialmente alrededor de backbone-service. Si esas áreas se veían afectadas por cambios de dominios no relacionados, el impacto operativo se sentía de inmediato. Una estructura en la que un cambio en blog o juego podía reiniciar servicios sensibles a la conectividad no era sostenible.
La segunda razón era el tiempo de despliegue. Incluso un cambio pequeño significaba reconstruir toda la aplicación grande, volver a generar la imagen y desplegar otra vez. A medida que el proyecto crecía, ese tiempo se sentía cada vez más costoso. Si las áreas que cambian rápido siguen atadas a una sola unidad de despliegue, todo el sistema se vuelve más lento.
Luego apareció el problema de los límites difusos de responsabilidad. Reglas de autenticación, excepciones comunes, HTTP interno, mensajería y almacenamiento de archivos estaban mezclados por todo el código, así que cada vez costaba más distinguir qué era común y qué pertenecía al dominio.
También había una razón más grande: yo ya tenía bastante claro lo que quería construir después. En Omnilude quería seguir creciendo en funciones de IA, quería expandir el servicio de juego y seguir sumando productos separados como el blog y las herramientas. Seguir haciendo crecer todo dentro de un solo backend dejó de parecer una buena decisión.
En otras palabras, esta transición no ocurrió porque la MSA se viera bien. Fue más bien una reestructuración necesaria para proteger los servicios sensibles a la conexión, reducir el tiempo de despliegue y seguir empujando ideas más grandes.
El primer paso no fue cortar servicios, sino sacar las reglas comunes
Aquí hay un punto importante. El primer paso de esta transición no fue separar auth-service ni ai-service. Antes de eso, creé common.
Esto importa mucho. Desde fuera, dividir servicios parece cortar dominios. En la práctica, el trabajo real empieza antes: primero hay que definir qué forma parte del contrato compartido y sacarlo del medio.
En Omnilude, common cumplió ese papel.
- Manejo común de excepciones
- Convenciones de JWT
- DTO compartidos
- Utilidades de la capa web
- Base de JPA y acceso a datos
- Ayudas para HTTP interno y mensajería
Una vez que esas piezas pasaron a common, los servicios posteriores siguieron hablando el mismo idioma en lugar de convertirse en aplicaciones totalmente desconectadas entre sí.
Una comparación sencilla sería acordar primero el estándar de los enchufes y de las manijas de las puertas antes de dividir un edificio en varias habitaciones. Si eso no se alinea antes, dividir los cuartos no hace la vida más fácil.
La separación avanzó muy rápido
Si uno mira el historial del repositorio, la transición avanzó en un lapso sorprendentemente corto. El esqueleto principal quedó armado entre el 18 y el 22 de enero de 2026.
Simplificando, el flujo fue así.
- Extraer
common - Separar
auth-service - Separar
backbone-service - Separar
storage-service - Empezar a separar
ai-service - Aislar lo que quedaba en
legacy-service - Extraer
game-service - Montar después
blog-servicecomo un servicio nuevo - Reorganizar Jenkins y Kubernetes para despliegue selectivo
Ese orden tenía su propia lógica.
auth-service tenía límites relativamente claros. Autenticación, cuentas y permisos son dependencias compartidas para otros servicios, así que separarlo temprano dio un punto de referencia fuerte para el resto.
backbone-service tenía un perfil más cercano a infraestructura, con eventos en tiempo real, mensajería y temas de DTE. Operativamente tenía mucho sentido separarlo de la lógica de producto que cambia más rápido.
storage-service estaba pensado para encargarse del contrato compartido de almacenamiento de archivos. Si el manejo de archivos vive en un solo lugar, blog, juego y resultados generados por IA pueden seguir el mismo flujo.
Después vino ai-service. Esa parte era más difícil porque era grande y tenía más dependencias. Aun así, era una de las áreas donde más pensaba experimentar, así que tratarla como servicio independiente tenía todo el sentido.
¿Por qué dejar legacy-service?
Cuando se habla de migrar a MSA, mucha gente imagina un final limpio en el que el monolito desaparece de una sola vez. En la práctica, ese final suele ser menos realista de lo que parece.
Por eso Omnilude agrupó en legacy-service los dominios que todavía no se habían extraído por completo.
Esa decisión no fue realmente una retirada. Fue más bien construir un amortiguador.
legacy-service hizo posibles varias cosas.
- No romper a la fuerza las partes que todavía no estaban migradas
- Permitir la convivencia de servicios nuevos y viejos bajo el mismo producto
- Seguir avanzando sin dejar de operar
- Posponer dominios más difíciles y extraer primero los más sencillos
Para mí, esa es una de las partes más reales de una transición a MSA. Una transición real suele parecerse menos a un corte perfecto y más a la creación de límites que puedan sostener la operación.
Juego, blog e IA quedaron más claros encima de esa base
A partir de ahí empezaron a definirse mejor los servicios con forma más clara de producto.
ai-service no es solo una API de chat. Carga responsabilidades como ejecución de agentes, workflows, pipelines de generación, generación de medios, sistemas de roleplay y agentes de producto. A ese tamaño, verlo como un servicio independiente es mucho más natural.
game-service estaba muy cerca de la parte que más quería hacer crecer dentro de Omnilude. A medida que se sumaron quizzes narrativos, escenarios y funciones de asset studio, se convirtió en un servicio centrado en dominio que unía IA, almacenamiento y autenticación.
blog-service es un caso un poco distinto. No fue simplemente un dominio sacado del monolito original. Se parece más a un servicio nuevo que encontró su lugar sobre una arquitectura ya separada. Como despliegue y reglas compartidas ya estaban divididos, el blog pudo diseñarse y operarse como servicio independiente desde el inicio.
Por eso esta transición a MSA no consistió solo en desmontar lo viejo. También fue la base para lanzar productos nuevos en unidades mejores.
El momento en que de verdad cambió todo fue el despliegue
Si uno mira solo el código, un proyecto puede parecer dividido. Pero el punto en el que la MSA se vuelve real es cuando cambia el despliegue.
En Omnilude eso está registrado en Jenkinsfile, Jenkinsfile.prd y kubernetes/services/**.
Hoy Jenkins puede desplegar selectivamente estos siete servicios.
auth-servicebackbone-servicestorage-serviceai-servicegame-serviceblog-servicelegacy-service
Eso significa que el repositorio puede seguir siendo uno solo, pero ya no es una aplicación que siempre deba desplegarse completa. Se convirtió en una estructura donde se puede publicar solo el servicio necesario.
Kubernetes sigue la misma dirección. Cada servicio tiene su propio Deployment y Service, mientras que Ingress enruta bajo api.omnilude.com o dev-api.omnilude.com según el prefijo de path.
Ese punto importa. MSA no consiste en dividir carpetas, sino en dividir unidades de operación. En Omnilude, solo después de pasar por ahí empecé a sentir que la separación era real.
Al final, lo importante no era la cantidad de servicios
Después de una transición así, es fácil fijarse en cuántos servicios salieron. Mirándolo ahora, el número no era lo importante.
Estas cuatro preguntas importaban mucho más.
- ¿Sacamos primero las reglas compartidas para que la separación posterior fuera posible?
- ¿Distinguimos dominios que cambian rápido de preocupaciones de infraestructura más lentas?
- ¿Creamos límites operables aunque todavía no fueran perfectos?
- ¿Conectamos la separación del código con la separación real del despliegue?
Visto así, la transición de Omnilude me parece bastante práctica. Puede que no sea el diagrama más bonito del mundo, pero sí creó una estructura capaz de sostener el siguiente producto y el siguiente experimento.
Cierre
La transición a MSA del backend de Omnilude no fue tanto el resultado de un gran documento estratégico desde el primer día. Se pareció más al tipo de limpieza estructural por la que tarde o temprano hay que pasar cuando se quiere construir algo más grande.
Empezó como un gran proyecto, extrajo common, separó auth, backbone, storage, ai, game y blog, dejó lo restante en legacy-service y finalmente alineó las unidades reales de despliegue con Jenkins y Kubernetes.
Para mí, eso se siente muy Omnilude. El nombre Omnilude lleva tanto la amplitud que sugiere omni- como el tono lúdico que sugiere -lude. La idea es que muchas funciones y servicios no se dispersen para siempre, sino que al final se junten en una experiencia de juego más grande. En ese sentido, esta transición también se parece al nombre: el destino estaba claro y la estructura siguió cambiando para acercarse a él.
En el próximo artículo quiero avanzar un paso más desde la arquitectura y contar cómo estoy implementando realmente el juego.