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.

19 KiB

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

Estado: propuesta técnica del 5 de octubre de 2026. El usuario fija tres capas —API, servidor y frontend— y sitúa la API en Go; solicita valorar también Go para servidor. La recomendación es Go para ambas capas de backend en un monolito modular, con frontend Svelte/TypeScript. La elección de Go para el servidor y el nuevo transporte se documentan como recomendación, no como migración ya aprobada o ejecutada.

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. La producción sigue siendo Node/Socket.IO/PostgreSQL.

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 recomendado. 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.

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.

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. Objetivo recomendado al publicar solo juegos propios.
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.
  • Cookie de sesión, Origin permitido y autenticación/CSRF antes de suscribir. El navegador no añade cabeceras arbitrarias a WebSocket: usar primer mensaje de autenticación con nonce/CSRF obtenido por HTTP, timeout corto y conexión todavía sin acceso; ningún secreto en query string.
  • 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.

La recomendación queda, por tanto, en frontend Svelte/TypeScript + API Go + servidor/motor Go, con separación lógica entre API y dominio y un backend modular al inicio. Su principal coste es portar y probar el runtime de juegos; los contratos y recursos ya diseñados siguen siendo una inversión aprovechable.

Powered by TurnKey Linux.