You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

25 KiB

Arquitectura de tres capas y evaluación de Go — Juegoland v2

Estado: actualizado el 6 de octubre de 2026. El usuario fija tres capas —API Go, servidor/motor propio Go y frontend Svelte/TypeScript— para games2. La dirección tecnológica está decidida; la propuesta de monolito modular, persistencia, motor y nuevo transporte requiere implementación y pruebas. El handoff distingue la base ejecutable de los contratos futuros.

El modelo de plataforma, sus políticas y la API definen comportamiento independiente del lenguaje. El SDK actual de diseño sí tiene firmas TypeScript y el formato v1 describe entradas de módulos: necesita una adaptación explícita para reglas nativas Go. Node/Socket.IO/PostgreSQL corresponden a la aplicación original games v1, no a una producción de games2. Este repositorio tiene una base Go/Svelte con salud, bootstrap y catálogo; aún carece de motor, identidad, PostgreSQL y tiempo real.

1. Las tres capas

Capa Responsabilidad Tecnología propuesta Límite
Frontend Pantallas, interacción, accesibilidad, vistas autorizadas, animación/audio y recuperación de conexión. Svelte/TypeScript; HTML/CSS y recursos propios. No conoce secretos, no decide edad/permiso, resultado o azar; no accede a DB.
API HTTP/tiempo real, negociación, decodificación y límites, sesión/CSRF/origen, correlación y traducción de errores. Go. No implementa reglas de juego ni duplica políticas en cada controlador.
Servidor Casos de uso, autorización de dominio, cuentas, salas, protección, motor, transacciones, plazos, moderación y publicación. Go. Es la única autoridad que confirma cambios. No depende de componentes UI ni llama a su propia API por HTTP.

API y servidor son separaciones de código/responsabilidad. En la primera topología se compilan y despliegan juntos; se llaman mediante funciones e interfaces Go. No hacen falta dos servicios de red ni bases de datos distintas para conservar tres capas. PostgreSQL, correo/SMS, almacenamiento de recursos y métricas son infraestructura de soporte.

La localización cruza las tres capas: el frontend presenta claves y formatos según preferencia; la API negocia idioma y etiqueta cada respuesta; el servidor conserva eventos, avisos y argumentos estructurados, sin convertir frases traducidas en reglas o datos autoritativos. La identidad visual y los tokens están pendientes de definición; las pantallas futuras deberán admitir expansión de texto y varias escrituras.

El perfil multidispositivo añade presentaciones TVBoard, Desktop y Mobil, con clientes player y shared-display. El servidor distribuye por suscripción y contrato las proyecciones player-full, player-controller y public-board. La API conserva vínculos duraderos de pantalla común por sala o TV personal por membresía y distingue acceso QR de mesa de enlace personal tras invitación. El cambio a móvil completo conserva actor/recibos y sustituye la suscripción. Go mantiene autoridad sobre acciones y tiradas; no se envía estado privado para filtrarlo en el televisor.

La pantalla puede ser una TV no smart: el renderer y la conexión residen en Fire TV, un receptor Chromecast, Google TV/Android TV o un ordenador por HDMI, según una vía verificada. Esos adaptadores se concretan en el perfil de compatibilidad; no modifican plazas ni permisos. El alcance gráfico decidido es 2D, incluida perspectiva, con sprites y animaciones sencillas, sin motor 3D, Three.js, iluminación o efectos avanzados. El usuario cuestiona el peso de Phaser y pide comparar Konva con Motion y su motor propio de vicen. Evaluar primero reutilización del motor propio para una presentación DOM; Konva queda condicionado a necesitar objetos/capas Canvas, y Phaser como alternativa de mayor alcance. La selección requiere pruebas de portabilidad, tamaño y rendimiento. Un adaptador conserva las barreras del coordinador visual propio y Svelte la interfaz/controles. Go sigue decidiendo reglas, azar y resultado. WebGL no es una dependencia global: cada renderer publica sus requisitos; Canvas tampoco se exige para todos los juegos. Phaser 4 y Phaser Editor 5 son motor y herramienta de edición respectivamente; el Canvas de Phaser está deprecado. Ninguna de estas bibliotecas acredita soporte universal en TV/Cast. La integración gráfica y el soporte Cast/apps de TV siguen pendientes de decisión, implementación y pruebas.

El perfil temporal propone reloj por época, mediciones acotadas y rondas con preparación, apertura, recogida y resolución. Los ingresos confiables deben persistirse antes de que el cierre ignore una respuesta; colas y commits no ordenan la reacción humana. Varias réplicas requieren autoridad temporal y fencing coherentes con la unidad de trabajo. Este perfil y su compensación siguen pendientes de esquemas, implementación y ensayos, separados de la simple recuperación por revisión.

El perfil de comunicación web/Flutter prepara una futura app independiente solo de controles, además del frontend web Svelte. Separa runtime de presentación, propone HTTPS/WSS común y SDK TypeScript/Dart sobre esquemas/fixtures neutrales. Flutter representa únicamente player-controller de TVBoard, con controles propios/declarativos compatibles y las mismas acciones al servidor; tablero y vistas completas permanecen en la web. «Jugar solo en el móvil» abre Mobil web mediante autenticación y transferencia de mando al mismo principal, conservando plaza/recibos. La app no ejecuta reglas Go ni bundles JavaScript por inferencia. Sesiones web/nativas tienen adaptadores de acceso distintos hacia la misma autorización. Implementar la app se deja para después de los contratos y el primer cliente web.

Los modelos compartidos tienen fuente JSON Schema y bindings generados Go/TypeScript/Dart. API, clientes y tareas usan DTOs públicos compatibles sin compartir entidades de base de datos ni clases de UI. ErrorData conserva la misma estructura e información en HTTP, mensajes y recibos; la causa interna y los datos de correlación de entrega tienen límites separados. Los bindings estructurales se complementan con validadores de recepción y relaciones, no se consideran prueba de autorización.

El perfil de jugadores virtuales propone controladores IA como tareas del servidor. Su soporte e instrucciones versionadas pertenecen a las propiedades de cada juego; el adaptador recibe solo la proyección del asiento y propone acciones fuera de la transacción. El servidor deriva el actor de la instancia autorizada y revalida el comando por el mismo caso de uso. Tareas y decisiones son duraderas; ni una llamada a modelo ni su memoria se incorporan a las funciones puras de reglas. Proveedores y credenciales se configuran en infraestructura, no en el paquete. Esta capacidad aún no está implementada.

flowchart LR
  USER[Navegador] --> PROXY[Proxy HTTPS]
  PROXY --> FE[Frontend Svelte]
  PROXY --> API[API Go: HTTP y tiempo real]
  subgraph BACKEND[Un backend modular Go]
    API --> APP[Casos de uso y autorización]
    APP --> DOMAIN[Identidad, salas y protección]
    APP --> ENGINE[Núcleo y reglas propias]
    JOBS[Plazos y tareas] --> APP
  end
  APP --> DB[(PostgreSQL)]
  DB --> OUT[Outbox y entregas autorizadas]
  OUT --> API
  APP --> PROVIDERS[Adaptadores de email, edad y SMS]
  FE --> ASSETS[Recursos públicos por digest]

El frontend puede servirse estático si se adopta una compilación compatible, o conservar SSR de SvelteKit con su proceso Node. Usar Go en el backend no elimina por sí solo Node del build o del SSR. Esta decisión de entrega se toma después de inventariar rutas/SSR, SEO y autenticación; no se exige rehacer Svelte.

2. Por qué Go encaja y qué no resuelve

Go proporciona compilación nativa y mecanismos de concurrencia adecuados para servicios de red; su documentación describe estos objetivos en la FAQ oficial. Para este producto, las ventajas esperadas son un backend tipado, distribución del ejecutable, herramientas de análisis/prueba y control explícito de cancelaciones, recursos y errores. Son razones de ingeniería, no una medición de que vaya a superar al servicio actual.

Los juegos por turnos no justifican por sí solos una reescritura por rendimiento. Persistencia, proyecciones por usuario, fanout, bloqueos y recuperación pueden dominar la latencia. Hay que medir el perfil de carga de las políticas en hardware documentado. Goroutines no sustituyen límites de concurrencia, colas acotadas ni bloqueos de base de datos.

El objetivo de capacidad de 100.000 jugadores simultáneos distingue personas, conexiones, comandos y entregas. Desde el primer juego se fijan estado duradero, recibos, límites, métricas y recuperación independiente del proceso; el broker y las réplicas se añaden cuando la carga lo exija. Esta arquitectura permite preparar varias réplicas y una autoridad transaccional por sala, pero aún no implementa ni acredita ese objetivo. La primera topología de un proceso es una fase de desarrollo, no una capacidad estimada.

Go tampoco resuelve errores del proxy, rutas/NAT, políticas incompletas, autorización o pérdida de mensajes. Mantener las invariantes importa más que el lenguaje. La API en Go será útil si centraliza entrega y llama a una autoridad de dominio clara; añadirla solo como segundo CRUD que escribe sobre los mismos datos que Node introduciría carreras.

3. Alternativas comparadas

Alternativa Ventaja Coste/riesgo Uso recomendado
API y servidor Node Menor trabajo inicial, reutiliza reglas y SDK TS. No satisface la dirección de API Go elegida. Línea base para comparar resultados y mantener producción durante el cambio.
API Go → servidor Node autoritativo Conserva motor y una sola transacción en Node. Dos procesos, salto de red, identidad interna, despliegue y trazas duplicados. Puente temporal si necesitamos API Go antes de portar el motor.
API y servidor Go, reglas Go incluidas Una autoridad, misma transacción, un runtime de backend y separación de capas por paquetes. Portar SDK, reglas, validadores y comprobar equivalencia. Dirección elegida para games2; implementación del motor pendiente.
Go + evaluador JS/WASM Podría conservar módulos de reglas o abrir varios runtimes. ABI, límites, compatibilidad JS, aislamiento, determinismo y coordinación añadidos. Fuera del primer alcance; exige una necesidad y un prototipo específico.

No se incluye un intérprete JS o un sistema de plugins dinámicos solo para simular que el SDK TS funciona en Go. Tampoco se cargan .go de un paquete en ejecución. Los juegos propios permiten registrar implementaciones compiladas y versionadas junto al servidor. Publicar nuevos gráficos/metadatos y publicar nuevas reglas tienen ciclos distintos.

4. Autoridad y transacciones

Una operación autorizada entra por un caso de uso común, tanto desde HTTP/tiempo real como desde tareas internas:

  1. Validar estructura, identidad, presupuesto técnico y contexto de autorización.
  2. Abrir la unidad de trabajo. Serializar autorización/revocaciones aplicables y bloquear agregados en orden global: contexto de permisos, sala, partida, registros dependientes. Toda ruta y tarea usa el mismo orden; relaciones de varias personas se ordenan por ID y se revalidan bajo bloqueo.
  3. Buscar recibo propio y comprobar idempotencia; si el comando es nuevo, comprobar permisos, revisiones, admisión y versión instalada.
  4. Ejecutar reglas locales, puras y acotadas con servicios deterministas. Validar transición y proyecciones.
  5. Confirmar sala, partida, resultado, tareas, recibo y outbox en una transacción PostgreSQL.
  6. Fuera de la transacción, entregar acuse/publicaciones con permisos actuales y ejecutar efectos externos idempotentes.

La documentación de transacciones Go distingue operaciones sobre sql.Tx de operaciones sobre el pool: mezclarlas puede ejecutar escrituras fuera de la transacción. Los repositorios participantes reciben la misma unidad de trabajo; no hacen commit interno ni abren otra conexión para «guardar el resultado».

Sin llamadas a SMS, verificador, correo, HTTP interno ni espera de UI dentro de un bloqueo. Los efectos externos usan tarea/outbox y conciliación. Un timeout no demuestra que un commit falló: se consulta/reintenta el mismo comando y se recupera su recibo. No se promete exactamente una entrega de red; se evita repetir el efecto de dominio.

La revocación debe cerrar entregas también si no llega otro comando: invalidación directa, reautorización de outbox e historial y purga de cachés controladas. Un snapshot con permisos antiguos no se publica por haber sido calculado antes de la revocación.

5. Qué se conserva del protocolo y qué requiere adaptación

Contrato actual Tratamiento con Go
IDs, sobres JSON, revisiones, recibos, estados, vistas por destinatario Conservar semántica y fixtures. No cambiar la versión de red solo por cambiar de lenguaje.
Manifiesto, recursos, barajas/dados/tableros, presentación y animaciones Conservar separación entre reglas privadas y recursos/UI públicos. El frontend sigue TS.
setup, acciones, validación, availableActions, project, projectEvent, onLifecycle Diseñar interfaces Go equivalentes; las firmas TS son referencia semántica, no código invocable desde Go.
rules.entry y compilación TS descrita por formato v1 Añadir un perfil de ejecución y distribución versionado antes de publicar paquetes Go. El esquema v1 no se modifica de forma incompatible manteniendo la misma versión.
Zod y sus refinamientos Zod puede seguir como herramienta de autoría/build. Go necesita validación estructural y semántica equivalente; generar tipos no reproduce refinamientos entre campos.
Lock/digests y reglas retenidas Relacionar paquete y versión con implementación compilada y build; conservar todas las versiones referenciadas por partidas activas.

El futuro perfil nativo tendrá un identificador de implementación registrado por el build, no una ruta suministrada por un jugador. Debe especificar runtime, versión de ABI/SDK, compatibilidad de estado y prueba de que el build contiene exactamente la implementación asociada al digest. Sus nombres/campos definitivos pertenecen a la siguiente versión del esquema de paquete, no se introducen como campos libres en v1.

No recompilar un módulo con cambios de reglas bajo la misma versión. Una build nueva puede contener varias implementaciones antiguas; readiness impide servir partidas cuyo módulo no esté disponible. El rollback conserva el conjunto completo necesario o detiene nuevas admisiones, nunca sustituye reglas sobre una partida existente.

La equivalencia entre runtimes exige vectores compartidos de estado, acciones, errores, proyecciones, resultados y eventos. En azar: mismo algoritmo/versionado y estado, consumo exacto de tiradas, rechazo de sesgo y reglas de barajado. No sustituir un RNG persistido por el generador estándar de Go. Datos dentro del rango seguro JSON, reglas claras para null/ausencia, Unicode, orden canónico, timestamps y mapas; no depender del orden de iteración Go ni de aritmética diferente.

Los juegos actuales que dependen de una librería JS, como ajedrez, necesitan equivalencia probada de reglas/casos extremos y revisión de licencia al portar o sustituir la dependencia. No basta con que las primeras jugadas parezcan iguales. Las vistas de Hundido deben probar además ausencia de filtraciones por jugador, eventos y errores.

6. HTTP, tiempo real y compatibilidad de transporte

Mantener HTTP JSON bajo /api/v2, contratos explícitos y errores de dominio de la API. La recomendación para el backend Go final es WebSocket estándar con un binding propio pequeño de los sobres ya definidos, evitando depender de una implementación no evaluada de Socket.IO en Go. La API documentada actualmente usa Socket.IO; la recomendación no cambia ese contrato automáticamente.

Socket.IO añade su propio protocolo; su documentación confirma que un cliente Socket.IO no se conecta a un servidor WebSocket simple (fuente oficial). Antes de adoptar el binding nuevo se debe publicar y probar:

  • Ruta y subprotocolo versionados, un sobre por mensaje de texto UTF-8, separación platform.*/game.* por contrato y límites propios; rechazo de binario y compresión hasta evaluar su necesidad/riesgos.
  • Autenticación antes de suscribir: web con cookie/origen/CSRF y futuro cliente nativo con su sesión emitida. El navegador no añade cabeceras arbitrarias a WebSocket; el perfil común propone desafío WSS, ticket breve obtenido por HTTPS y primer mensaje de autenticación, con timeout y sin datos antes de autenticar. Ningún secreto en query string; el runtime declarado no sustituye credenciales.
  • Negociación, ping/pong, timeouts del proxy, cierre/drenaje, backoff con jitter, reconexión, cursores, límites de buffers y recuperación tras segundo plano móvil.
  • Recibos duraderos y barreras iguales a las del protocolo de aplicación. No confundir confirmación de transporte con commit.
  • Política explícita cuando no se puede abrir WebSocket: mensaje recuperable y reintento acotado; cualquier fallback HTTP requiere rutas y pruebas de equivalencia. No prometer long-polling si no se implementa.

Durante la transición /socket.io sigue con Node para clientes/partidas que lo requieran. El endpoint nuevo negocia de forma distinta; no se redirige el protocolo antiguo al nuevo como si fueran intercambiables. Un adaptador de compatibilidad, si se necesita, traduce transporte hacia la misma autoridad, nunca aplica dos comandos.

7. Organización de código propuesta

Estructura orientativa, no directorios ya creados:

cmd/juegoland/                 composición y arranque
internal/api/http/            handlers, decodificación, CSRF, errores
internal/api/realtime/        conexión, negociación, suscripciones
internal/application/         casos de uso y unidad de trabajo
internal/domain/identity/     cuentas y sesiones
internal/domain/safety/       edad, teléfono, tutela y permisos
internal/domain/rooms/        salas, miembros e invitaciones
internal/domain/chat/         comunicación y redacción
internal/domain/moderation/   casos, restricciones y recursos
internal/engine/              núcleo determinista y versiones
internal/games/               reglas propias registradas por build
internal/infra/postgres/      repositorios, migraciones y outbox
internal/infra/providers/     email, SMS y verificador
internal/jobs/                tareas que invocan casos de uso
contracts/                    esquemas y fixtures compartidos

El dominio no importa HTTP, Socket.IO, drivers SQL o SDK de proveedor. API invoca aplicación; infraestructura implementa interfaces requeridas por esta. El punto de composición conecta implementaciones. No crear una jerarquía vacía por cada entidad ni llamar «microservicio» a un paquete.

Se adopta una versión soportada de Go al implementar, fijada en toolchain/CI; driver, router y librería WebSocket se seleccionan con mantenimiento, licencias y compatibilidad comprobados. No se instala ni contrata nada en esta fase de especificación.

8. Migración sin doble autoridad

  1. Completar contratos de plataforma y fixtures compartidos; inventariar dependencias/estado de los juegos actuales. Mantener producción estable.
  2. Prototipo vertical Go con Conecta 4: cuenta sintética, sala, comando, commit, outbox, reconexión y reinicio con PostgreSQL. Probar también revocación concurrente. Comparar con Node; aún sin tráfico real.
  3. Probar Hundido para información oculta/acciones independientes y Brisca para azar/mazos. Fijar SDK Go, perfil de paquete y binding antes de migrar el resto.
  4. Si se necesita API Go mientras el dominio sigue Node, Go delega la operación completa al dueño Node, sin escritura parcial. Canal interno autenticado, origen no público, identidad verificable y deadlines; Node sigue validando permisos y controla toda la transacción. Una identidad en cabecera sin autenticar no vale.
  5. Preferir drenar salas activas antiguas en su propietario y enviar nuevas salas por cohortes al backend nuevo. No repartir la escritura de una misma sala entre Go y Node. Identidad y protección mantienen una autoridad común, y una revocación afecta a ambos antes de nuevas entregas.
  6. Si un traslado de partida activa fuera imprescindible: detener nuevas escrituras, bloquear y cambiar propietario con generación/fencing duradero, migrar snapshot validado sin repetir setup, confirmar antes de enrutar. El servidor anterior rechaza su generación obsoleta. No activar este camino sin pruebas de caída en cada paso.
  7. Retirar Node de juego solo cuando no queden partidas/compatibilidades que dependan de él. SSR/build de frontend se evalúan por separado. Rollback mantiene contratos, esquema, versiones y políticas de seguridad; no vuelve a habilitar chat a menores.

No existe una transacción sql.Tx compartible entre procesos Go y Node. Tampoco soluciona ese problema «usar la misma base de datos» con commits independientes. Si en un futuro Go calcula mediante un evaluador remoto sin efectos, deberá hacerlo fuera de locks y confirmar por comparación de todas las revisiones/lecturas relevantes; eso es otro diseño y no la topología inicial recomendada.

9. Criterios de decisión antes de implementar el cambio completo

  • Go conserva equivalencia semántica de reglas, filtros privados, RNG y recibos, y puede instalar/retener versiones de juego.
  • Existe un único dueño por agregado, una unidad de trabajo para sala/partida y revocación consistente entre interfaces.
  • Fixtures JSON pasan en Go y TS, incluidos casos negativos, refinamientos y canonicalización; ningún generador se trata como prueba de equivalencia.
  • Pruebas de integración con PostgreSQL, carreras, proceso detenido tras commit, restart, outbox y cliente lento; carrera de datos Go en pruebas pertinentes.
  • Perfil de carga y restauración de las políticas medidos en el despliegue candidato; frontend móvil y accesibilidad verificados.
  • Para anunciar 100.000 jugadores simultáneos, superar la matriz de carga, fallos y presupuesto de capacidad con el número real de conexiones derivado.

La dirección elegida es frontend Svelte/TypeScript + API Go + servidor/motor Go, con separación lógica entre API y dominio y propuesta de backend modular al inicio. Su principal coste pendiente es implementar y probar el runtime de juegos; los contratos y recursos ya diseñados sirven de referencia para ese trabajo.

Powered by TurnKey Linux.