Saltar a contenido

ADR-025 · Service topology standard (4 patterns canónicos A/B/C/D)

Fecha: 2026-04-29 Source: /srv/projects/cis/cis-plan/DECISIONS.md (do not edit here — re-split desde la fuente)


Contexto

El audit acdf956053dd737dd (agent-c, 2026-04-29) reportó que la división backend/frontend en CIS no es uniforme: es inconsistencia heredada, no decisión deliberada. Casos representativos:

  • cis-admin legacy corre 3 stacks coexistentes: Django + FastAPI :8244 + Next.js :8245. ADR-010 ya decidió colapsar a Vite SPA + FastAPI (B), pero la migración (Fase E) está en curso.
  • cis-mailer es FastAPI + UI Jinja2 mínima (A monolítico). Correcto.
  • cis-pagos es FastAPI puro + dos clientes externos consumen su API (A monolítico). Correcto.
  • periodismo2 es un cluster de 4 servicios (-www, -api, -admin, -frontend) con PYTHONPATH cruzado. Topología no canónica heredada de migración previa.
  • cis-isometrico es FastAPI + worker -render separado (D workers). Correcto.

Sin un canon escrito, cada servicio nuevo elige su topología por inercia o copy-paste del último. Esto produce 2 systemd units cuando 1 alcanza, o PYTHONPATH cruzado cuando un solo backend con 2 routers resuelve.

Alternativas consideradas

  1. (a) Sin canon — cada servicio elige libremente
  2. Pro: flexibilidad máxima.
  3. Contra: estado actual. Produce inconsistencia. Cuesta razonar sobre el ecosistema. Cutover de un servicio split (cis-admin Fase E) toma coordination de múltiples units.

  4. (b) 4 patterns canónicos con migration path para deudapropuesta

  5. Pro: cubre los casos reales (no fuerza 100% monolítico ni 100% split). Migration path documentado para servicios que heredan topología no canónica.
  6. Contra: define 4, no 2. Más superficie mental que un canon estricto.

  7. (c) Forzar microservicios separados (backend + frontend siempre split)

  8. Pro: separación clara siempre.
  9. Contra: doble systemd unit por servicio. Build pipeline duplicado. Deploy lockstep complejo. Para servicios sin UI rica (cis-mailer, cis-pagos, cis-monitoreo) es overkill.

  10. (d) Forzar monolito siempre (FastAPI + Jinja templates o SSR React)

  11. Pro: simple, una unit.
  12. Contra: paneles ricos como cis-admin se quedan con UX pobre. Vite+React+TS no cabe naturalmente en monolito sin SSR.

Decisión

4 patterns canónicos. Cada servicio nuevo cae en uno y se documenta en su AGENT-CONTEXT.md:

Pattern A · Monolítico

  • Forma: FastAPI + UI Jinja2 mínima (templates server-side) · 1 systemd unit.
  • Cuándo: servicios sin UI rica (admin con 2-3 pantallas, dashboard simple, API pura con health UI).
  • Naming: 1 unit cis-<svc>.
  • Ejemplos canon: cis-mailer (UI dashboard de templates + logs), cis-monitoreo (UI status), cis-pagos, cis-core, cis-sii-gateway, cis-claudia (host MCP + chat backend; UI rica vive en cis-admin/chat, no acá).

Pattern B · Backend + SPA estática

  • Forma: FastAPI API + Vite dist/ servido por Caddy file_server · 1 systemd unit (la SPA es archivos estáticos, no proceso).
  • Cuándo: paneles internos con UI rica sin necesidad de SEO/SSR.
  • Naming: 1 unit cis-<svc>-backend o simplemente cis-<svc>.
  • Caddy: bloque dual con handle /api/* → reverse_proxy backend, default → root /srv/projects/cis/<svc>/dist; file_server.
  • Ejemplos canon: cis-admin SPA nueva (post-Fase E), cis-infra, cis-mando (UI server-side hoy, candidato a B en v0.3).
  • Pattern preferido para nuevos paneles internos.

Pattern C · Backend + Frontend SSR

  • Forma: FastAPI API + Next.js SSR/ISR · 2 systemd units (cis-<svc>-backend, cis-<svc>-frontend).
  • Cuándo: solo cuando SEO/ISR justifican el costo. Cara al público con artículos indexables, contenido cacheable por edge.
  • Ejemplos canon: periodismo2-frontend (cara al mundo, índice Google).
  • Antipattern detectado: cis-admin legacy usaba Pattern C sin necesidad SEO (es internal, noindex). ADR-010 lo migra a B.

Pattern D · Workers / sync separados

  • Forma: backend + N units adicionales -sync, -render, -worker · N+1 systemd units.
  • Cuándo: procesos async legítimos (poll IMAP cada N min, render mesh OSM, queue arq, transcripción Whisper).
  • Naming: cis-<svc> (web), cis-<svc>-sync / cis-<svc>-render / cis-<svc>-worker.
  • Ejemplos canon: cis-inbox (web cis-inbox-backend + cis-inbox-sync IMAP poll), cis-isometrico (cis-isometrico-render para meshes), cis-usaia (queue Whisper).

Reglas vinculantes:

  • 1 systemd unit por componente real (proceso). No multiplicar units sin razón. Un cron interno con arq o apscheduler no requiere unit separada — vive en el proceso del backend.
  • Naming: cis-<svc> para componente único; cis-<svc>-<role> cuando se necesita disambiguar (-backend, -frontend, -sync, -render, -worker).
  • AGENT-CONTEXT.md declara explícitamente el pattern: topology: A|B|C|D.

Migration path para deuda:

  • cis-admin legacy (Django + FastAPI :8244 + Next.js :8245) → colapsa a B cuando Fase E cierre. ADR-010 + P0.1 ejecuta. Resultado: 1 unit cis-admin-backend + Caddy sirve SPA estática.
  • periodismo2 cluster (4 sub-servicios con PYTHONPATH cruzado) → consolidar a backend único (-api + -admin merged como 1 unit con 2 routers) + frontend SSR (-frontend) + worker (-render). Resultado: 3 units en lugar de 4. Documentado como tarea T3 en BACKLOG flujo 11. Migration coordinada (Wave 3) por dependencia PYTHONPATH.
  • cis-claudia v1 (subprocess Claude Code monolítico) → Pattern A en v2 (ADR-015). Sin worker separado en MVP; arq queue interna si crece.

Consecuencias

  • Positivo: cualquier servicio nuevo cae en un pattern conocido. Cutover y deploy procedure son predecibles. Topología no se elige por inercia. Migration path documentado para los 2 casos de deuda más visibles.
  • Negativo: la línea entre A y B no siempre es obvia (cuándo "una pantalla más" justifica saltar a Vite SPA). Heuristica: si la UI tiene >5 vistas con state cliente significativo → B. Si es <5 vistas + form submit + tabla → A.
  • Riesgo: PYTHONPATH cruzado en periodismo2 es deuda persistente; consolidación toma 2-3 semanas. Mientras dure, el cluster sigue siendo no-canónico. Documentado como conocido.
  • Migración: cis-admin Fase E (P0.1, semana 1) cierra el primer caso. periodismo2 consolidación (Wave 3, semanas 4-6) cierra el segundo. Servicios nuevos arrancan canónicos.
  • Compatibilidad: servicios canónicos hoy (cis-mailer, cis-pagos, cis-monitoreo, cis-core, cis-inbox, cis-mando) ya cumplen — no se tocan. La regla aplica a partir de la fecha del ADR.
  • Documentación: cada AGENT-CONTEXT.md agrega field topology: A|B|C|D + "components" listando units. Generador regenerate-ecosystem.sh (ADR-016) lo lee para el mapa global.