Tecnología

Para construir Omnilude, estoy creando este backend

AAnonymous
13 min de lectura

Este artículo no es una presentación de una sola funcionalidad. Es un intento de ordenar sobre qué tipo de estructura se está construyendo el proyecto llamado Omnilude y por qué estoy organizando el backend de esta manera.

Lo que estoy haciendo ahora mismo tiene dos líneas superpuestas. La primera es poner a prueba los límites de los coding agents. Sigo comprobando hasta dónde puede llegar la implementación con instrucciones cercanas al vibe coding y al flujo de conciencia, y si esa forma de trabajo puede extenderse de verdad al desarrollo de nivel producción. La segunda es no dejar ese experimento solo como experimento, sino hacer que termine convertido en un servicio real. Omnilude está en el centro de ese servicio, y sobre esta estructura quiero empujar el experimento a través del tipo de desarrollo de juegos que antes quería hacer.

Por eso este texto se parece más a una introducción estructural que a una vitrina de implementación. Pero tampoco quería explicarlo solo enumerando nombres de servicios. Este backend está dividido en varios módulos, aunque la forma en que yo lo miro es un poco distinta. Yo veo todo esto como un solo backend de producto. Autenticación, ejecución de IA, gestión de contenido, conexiones en tiempo real, almacenamiento de archivos y flujos de eventos están atados hacia un mismo objetivo.

Puede que este sea un post largo y algo denso, pero quiero explicarlo de la forma más concreta posible.

Qué tipo de proyecto quiere ser Omnilude

Para mí, Omnilude tiene dos significados al mismo tiempo.

El primero es el de experimento. En un momento como este, no creo que para un desarrollador sea suficiente con probar herramientas nuevas. Es muy probable que la distancia entre quienes usan bien la IA y quienes no la usan bien siga creciendo, y creo que esa diferencia pronto se abrirá menos por la velocidad de implementación y más por la definición del problema, el diseño de la estructura y la forma de validar. Yo no quería limitarme a observar ese cambio. Quería atravesarlo con el cuerpo mientras construía un producto real.

El segundo es un objetivo de producto muy concreto. Desde hace mucho tiempo quiero hacer juegos. Y ahora ya no quiero acercarme a ese objetivo solo con la forma antigua de trabajar. Quiero intentarlo conectando en un mismo flujo herramientas de producción potenciadas por IA, una pipeline de generación de contenido, un backend operable e incluso experiencias en tiempo real. Omnilude está colocado exactamente en esa dirección.

En otras palabras, este proyecto es a la vez un experimento con agentes de IA para aprovechar al máximo la IA y un proyecto de desarrollo de producto que al final tiene que converger en un servicio que realmente pueda lanzarse.

Varios servicios, vistos como un solo backend de producto

Tomando como referencia el momento en que escribo este texto, el backend de Omnilude sigue siendo un único servicio monolítico. Pero yo no veo ese monolito como si fuera solo un servidor enorme. Lo veo como una estructura en la que conviven múltiples fronteras de responsabilidad. Los auth-service, ai-service, blog-service, game-service, storage-service, backbone-service, mmorpg-service y la base compartida common(jspring) que aparecen en este artículo no son tanto unidades de despliegue de hoy como criterios con los que entiendo y expando este sistema. En este texto voy a llamar a esa capa compartida interna simplemente common.

Por eso, cuando digo varios servicios, pero vistos como un solo backend de producto, no intento exagerar la estructura actual. Es más bien una forma de explicar qué responsabilidades ya están separadas dentro de un sistema que todavía sigue unido y en qué dirección esas fronteras pueden volverse más nítidas. Lo importante no es la cantidad de servicios en sí, sino si cada responsabilidad está unida hacia un mismo producto sobre un conjunto compartido de reglas.

Más que ver el Omnilude actual como un único servidor gigante, lo veo como una estructura en la que coexisten varios límites dentro de un mismo backend de producto. Más adelante quiero dejar en otro post el proceso por el cual esos límites se separan en repositorios y unidades de despliegue reales. En este texto quiero desplegar, con el vocabulario que uso hoy, la separación de responsabilidades que ya existe dentro del monolito actual.

El siguiente diagrama es una vista general del backend no legacy actual, reagrupado y visto como un solo backend de producto.

Hay tres puntos que para mí son los más importantes en este diagrama.

El primero es que common forma el piso de todo el sistema. Cada servicio existe de forma independiente, pero las convenciones de autenticación, los patrones de llamadas HTTP internas, las rutas de publicación de eventos, el manejo de storage y el procesamiento de trabajos distribuidos se repiten sobre una misma base compartida. Eso es lo que evita que la estructura se disperse.

El segundo es que los dominios de producto y la infraestructura operativa están separados. auth, ai, game, mmorpg y blog representan capacidades del producto, mientras que storage y backbone se encargan de la base operativa común que esas capacidades necesitan para funcionar de verdad.

El tercero es que esta separación también hace que la estructura sea mejor para trabajar con IA. Los coding agents no manejan bien un codebase gigantesco cuando no hay límites claros de contexto. En cambio, funcionan con mucha más estabilidad en una estructura donde los roles están separados y las reglas son explícitas. Estoy diseñando esta arquitectura no solo para que las personas la mantengan mejor, sino también para darle a la IA un mejor entorno de trabajo.

En el centro de esta estructura está common

Lo primero que hay que explicar para entender este proyecto es common, porque al final esa es la razón por la que todo el sistema puede moverse como si fuera un solo producto.

common no es solo una colección de utilidades. Yo lo veo más cerca de un SDK de plataforma interna. Si uno mira cómo se conectan los servicios entre sí en la práctica, descubre que muchas cosas ya están estandarizadas dentro de common.

  • common-core ofrece la base más baja, como JWT, excepciones compartidas y utilidades básicas.
  • common-web ofrece el cliente HTTP interno y las convenciones compartidas de la capa web.
  • common-data agrupa JPA, QueryDSL y la base común de acceso a datos.
  • common-messaging unifica la ruta de publicación de eventos.
  • common-cloud estandariza integraciones de manejo de archivos, como storage commits y access URLs.
  • common-dte ofrece el flujo de trabajos distribuidos, workflows y job events.

La razón por la que creo que esta capa importa es clara. En el desarrollo AI-first, más importante que qué tan rápido se escribe código es cuánto patrón repetible ya tiene el sistema. Si cada funcionalidad nueva obliga a una persona o a un agente a entender una estructura totalmente distinta, la productividad cae muy rápido. En cambio, si la base compartida es fuerte, el problema puede dividirse en unidades más pequeñas y los puntos de revisión quedan mucho más claros.

Por ejemplo, cuando game-service llama funcionalidades de IA, no se conecta sin más a modelos externos, sino que llama internamente a ai-service. El almacenamiento de archivos también está organizado para que cada servicio no maneje S3 por su cuenta, sino que pase por storage-service. Y cuando hace falta, los eventos fluyen alrededor de backbone-service. Gracias a esa consistencia, puedo darle instrucciones más precisas a la IA y validar los resultados mucho más rápido.

Esta estructura también importa desde la perspectiva del desarrollador. Aunque parezca que hay muchos servicios, en realidad comparten el mismo lenguaje, el mismo framework, las mismas convenciones y la misma plataforma interna. Eso hace que la unidad de pensamiento no se fragmente por completo. Creo que esa es la mayor fortaleza de la estructura de Omnilude.

Por qué los dominios de producto están separados así

Ahora quiero explicar con algo más de detalle qué papel cumple cada servicio. Más que listar nombres, importa entender por qué están divididos de esa manera.

auth-service: el punto de partida de la confianza

auth-service se encarga de login, cuentas, dispositivos, permisos, créditos y registros de actividad. En la superficie puede parecer un servidor de autenticación bastante familiar, pero yo no lo veo como una simple API de login. Este servicio define la frontera de confianza de todo el producto.

Quién es el usuario, qué sesión tiene, qué permisos posee y qué créditos o historial de uso acumula, casi todo empieza aquí. Si otros servicios van a experimentar y sumar funciones con mayor velocidad, esta capa básica de confianza tiene que ser más conservadora y más explícita, no menos.

Y hay algo interesante: este servicio no está completamente aislado. En el arranque sincroniza configuraciones de plataforma con backbone-service y ayuda a formar la convención compartida de JWT de todo el backend. En otras palabras, la autenticación es una capacidad independiente y, al mismo tiempo, un lenguaje común que conecta el sistema completo.

ai-service: el hub de ejecución de IA

ai-service es el servicio más simbólico de este proyecto. Aquí se concentran las llamadas a LLM, los workflows multiagente, la generación de medios, los agentes de producto, el roleplay y varias APIs de IA a nivel sistema.

Lo importante es que este servicio no es solo un proxy para APIs de modelos externos. Yo lo veo como un hub de ejecución de IA. Es la capa central que decide qué modelo llamar, qué workflow seguir, qué eventos emitir, qué resultados guardar y cómo transmitir el progreso hacia afuera.

Creo que esta estructura va a ser todavía más importante en adelante. En los proyectos que usan IA de manera agresiva, cuando las llamadas a modelos externos empiezan a dispersarse por todas partes, el control se vuelve difícil casi de inmediato. Si se piensa en costos, manejo de fallos, convenciones de prompts, registros de ejecución, almacenamiento de resultados y estrategia para cambiar de proveedor, es mejor que la IA esté coordinada en un solo lugar. Ese es exactamente el rol que asume ai-service.

game-service: el dominio que quiero volver real primero

game-service es el dominio central de producto en este proyecto. Aquí se reúnen capacidades como escenarios, story quiz, sesiones de juego, metadata compartida del juego y asset studio.

La razón por la que este servicio importa es simple. Lo primero que quiero lanzar dentro de Omnilude termina conectado con este dominio. Por eso no está diseñado como un simple almacén de datos, sino como un dominio que interactúa con IA más que cualquier otro. La creación de escenarios, la generación de assets, la creación de sesiones y la operación del flujo de juego convergen aquí.

Y hay un punto especialmente importante: game-service está conectado de forma directa con ai-service. La generación de contenido, la revisión, las tareas de apoyo, la producción de assets y parte de los flujos de automatización inevitablemente van a acelerarse con IA. En vez de esconder esa conexión, me pareció mejor hacerla visible en la propia estructura.

mmorpg-service: el runtime en tiempo real como otro eje

mmorpg-service pertenece a la misma área de juego, pero su naturaleza es muy diferente. Este servicio no solo ofrece APIs de administración basadas en HTTP. También opera un runtime WebSocket en tiempo real sobre un puerto de gateway separado.

Si alguien pregunta por qué no lo mezclé dentro del mismo servicio, la respuesta es clara. Las APIs de contenido centradas en CRUD y los servidores de juego en tiempo real tienen modos de fallo, exigencias de rendimiento y patrones de manejo de estado completamente distintos. Creo que es más correcto no tratarlos dentro de la misma unidad mental.

La forma de autenticación también es interesante. En el momento de la conexión, la convención JWT emitida por auth-service se valida localmente dentro del gateway de mmorpg-service. Es decir, el runtime no vuelve a llamar al servicio de autenticación por HTTP cada vez. Esta estructura muestra muy bien dónde un sistema en tiempo real debe separarse de las APIs de negocio normales.

blog-service: la memoria externa del proyecto

blog-service puede parecer relativamente menos complejo, pero para mí ocupa un lugar importante. No es solo un CMS para operar un blog. Es el canal a través del cual dejo acumulado, hacia afuera, qué estoy construyendo, qué decisiones estoy tomando y qué experimentos estoy haciendo.

Si Omnilude no va a ser solo un proyecto que vive encerrado por dentro, entonces su estructura, sus experimentos y su proceso de prueba y error tienen que poder explicarse hacia afuera. blog-service se encarga de ese registro. Por eso aquí se reúnen posts, comentarios, traducciones, SEO y enlaces a assets estáticos. El producto necesita una capa de comunicación externa.

Por qué la infraestructura operativa está separada

Un servicio no funciona solo con capacidades de producto. Hace falta una capa que almacene resultados, gestione ciclos de vida de archivos, transmita progreso, transporte eventos, siga trabajos de larga duración y mantenga conexiones en tiempo real.

En Omnilude, ese rol lo asumen principalmente storage-service y backbone-service.

storage-service: la vida real de los archivos se administra aquí

En muchos proyectos, el almacenamiento de archivos se trata como una capacidad que se agrega al final. Pero en un producto real importan la carga de archivos, los estados temporales, commit/cancel, la access URL, la thumbnail URL y la metadata de objetos estáticos. Los archivos no se suben y ya está. Tienen un ciclo de vida.

Por eso separé storage-service. Ya sea una imagen del blog, un resultado creado por IA o un asset del juego, intenté integrarlo todo lo posible en esta misma ruta. Así, sin importar qué servicio manipule un archivo, puede hacerlo bajo las mismas reglas. Además, si después cambian la política de acceso o la estrategia de almacenamiento, el impacto queda más acotado.

backbone-service: el centro de la operación y de lo asíncrono

backbone-service, como su nombre sugiere, está muy cerca de ser la columna vertebral. Aquí se concentran el hub de eventos, SSE, WebSocket, la configuración de plataforma y el flujo de eventos de DTE Job.

Este servicio es especialmente importante porque encaja muy bien con la ejecución de IA. Muchas de las funciones que estoy construyendo no terminan en una sola API sincrónica. Cuando el usuario inicia una tarea, hacen falta avances intermedios, un evento de finalización y, en algunos casos, una conexión o una notificación en tiempo real. Si cada servicio de dominio empieza a resolver esas necesidades por separado, la estructura se desordena muy rápido.

Por eso reuní en backbone la entrega asíncrona y la capa operativa en tiempo real. Veo mucho más manejable un enfoque donde la ruta por defecto de los eventos se organiza como HTTP -> backbone -> Kafka/RabbitMQ, y el progreso relacionado con DTE se transmite por SSE.

El siguiente flujo simplifica cómo se conecta en la práctica un trabajo de generación con IA.

La razón por la que me gusta esta estructura es clara. ai-service se encarga de la generación, storage-service del almacenamiento de resultados y backbone-service de entregar el progreso hacia afuera. Como las responsabilidades están separadas, cada parte puede validarse con mucha más claridad.

Así uso las capas de datos y mensajería

Para ver la estructura de forma más tridimensional, también hay que mirar las capas de almacenamiento y mensajería. El backend no legacy actual de Omnilude no mezcla infraestructura de forma indiscriminada. El papel de cada capa es bastante claro.

  • PostgreSQL es el almacenamiento persistente por defecto para casi todos los servicios. auth, ai, blog, game, storage, backbone y mmorpg comparten ese eje.
  • Redis se usa menos como caché simple y más para Pub/Sub y entrega de eventos orientados a sesión. Está extendido entre varios servicios, pero su propósito es bastante estratégico.
  • Cassandra hoy está más cerca de la capa de activity logs y escritura de eventos de auth-service y backbone-service. Existe un límite para que no todos los servicios se conecten sin criterio.
  • Kafka y RabbitMQ son manejados en la práctica con backbone-service en el centro. En lugar de que cada servicio se conecte directamente al broker de mensajes, primero envían eventos a backbone y backbone los propaga hacia la capa externa de mensajería.
  • S3 es manejado directamente por storage-service, y los demás servicios pasan por él siempre que es posible. Es una decisión tomada para concentrar en un solo lugar las reglas sobre el manejo de archivos.

A primera vista, esto puede parecer un poco indirecto. Pero yo creo que este tipo de separación importa mucho más a largo plazo. Si los servicios funcionales empiezan a asumir directamente todos los caminos de storage y mensajería, los límites de responsabilidad se vuelven borrosos con mucha rapidez. En cambio, si el acceso a la infraestructura está ordenado a través de servicios tipo gateway como ahora, luego es mucho más fácil cambiar políticas operativas o acotar puntos de falla.

Esta estructura también es ventajosa cuando trabajas con IA. Si está claro hasta dónde una funcionalidad resuelve algo por sí misma y desde dónde debe pasar por una ruta compartida, los resultados que obtienes de un agente son mucho más estables.

Por qué las solicitudes autenticadas y las conexiones en tiempo real siguen rutas distintas

Para entender con más detalle la estructura de Omnilude, conviene mirar por separado las solicitudes sincrónicas y las conexiones en tiempo real.

Las consultas de contenido o tareas de administración habituales siguen, en su mayoría, un flujo en el que el usuario inicia sesión en auth-service y luego llama a una API de dominio. En ese caso, lo importante es que se mantengan la convención compartida de JWT y el modelo de permisos. Como blog-service, game-service y ai-service comparten esas reglas, el usuario obtiene una experiencia relativamente consistente aunque los servicios estén separados.

Las conexiones en tiempo real, en cambio, tienen requisitos diferentes. mmorpg-service, en particular, usa un puerto de gateway separado en :9088/ws. El usuario ya tiene un token emitido por auth-service, y el gateway lo valida localmente. La estructura está pensada para establecer sesiones rápidas sin depender de un viaje de ida y vuelta al servidor de autenticación cada vez.

El siguiente flujo muestra esa diferencia.

Esta diferencia importa más de lo que parece. Con esta estructura quería dejar claro que las APIs de negocio normales, los trabajos largos de IA y las sesiones de juego en tiempo real necesitan centros de gravedad distintos. Hacer que todo siga una sola forma puede parecer simple al principio, pero en operación real muchas veces solo agranda los problemas.

Cómo se conecta esta estructura con el desarrollo AI-first

Llegados a este punto, puede aparecer una pregunta. ¿Por qué una estructura así se relaciona con usar bien la IA?

Creo que esa pregunta importa mucho, porque muchos desarrolladores todavía ven la IA poco más que como una herramienta que escribe código por ellos. Pero el punto en el que la productividad realmente da un salto no es cuando simplemente generas más código, sino cuando el sistema empieza a tener una estructura que la IA puede entender mejor.

Por ejemplo:

  • Un conjunto de servicios con responsabilidades separadas funciona mejor para dividir prompts que un único servicio gigante con roles borrosos.
  • Una estructura con una plataforma interna como common le da a los agentes un contexto mucho más estable que un codebase sin convenciones compartidas.
  • Cuando el manejo de archivos, de eventos, la ejecución de IA y las conexiones en tiempo real están separados, se vuelve más fácil rastrear en qué frontera está ocurriendo un problema.
  • También quedan mucho más claros los puntos que una persona debe revisar al final.

Al final, el desarrollo AI-first no consiste en pegar IA por todas partes. Se parece más a ordenar el sistema para que la IA pueda trabajar con mayor consistencia. Ese es exactamente el punto que estoy poniendo a prueba mientras construyo Omnilude.

Desde esa perspectiva, creo que lo que hoy necesitan los desarrolladores es entrenamiento más que noticias sobre modelos. No solo entrenamiento para escribir mejores prompts, sino para construir mejores estructuras, revisar con mejores criterios y diseñar el contexto en el que la IA puede trabajar. Creo que este proyecto, al final, tiene que convertirse en el resultado de ese entrenamiento.

El destino sigue siendo lanzar Omnilude

Hay una razón por la que no quería que este artículo se quedara solo en la arquitectura. Esta estructura no existe para explicarse. Existe para construir algo que al final pueda lanzarse de verdad.

Lo que estoy haciendo ahora es construir herramientas de producción basadas en IA e implementar encima una plataforma de juegos. Este backend está diseñado para que esas dos líneas se encuentren. La IA empuja la producción de contenido, el dominio de juego convierte eso en experiencia de producto y la infraestructura operativa eleva el conjunto hasta un nivel capaz de sostener un servicio real.

Yo todavía quiero lanzar un juego bien hecho. Y quiero comprobar por mí mismo si ese proceso puede hacerse posible no solo con intuición y entusiasmo, sino también con mejor estructura, experimentación más rápida y validación más precisa. Para mí, Omnilude es el proyecto donde se reúne esa expectativa.

Por eso, en este blog no pienso escribir solo sobre arquitectura. Quiero seguir dejando registro de qué funcionalidades se están incorporando de verdad, hasta dónde está empujando la IA, en qué puntos debe juzgar la persona y si esta estructura realmente conduce a un producto que pueda lanzarse.

Ahora mismo estoy presentando la estructura del backend, pero la pregunta más importante en mi cabeza es una sola: ¿de verdad esta forma de trabajar puede llegar hasta el final?

No quiero responder esa pregunta solo con palabras. Quiero comprobarlo construyendo Omnilude de verdad.