Saltar a contenido

ADR 021: IO de dominio (events, listeners, observers, middleware, notifications, broadcasting)

Estado

Aceptado — 2026-07 · Implementado (beta)io.events, io.observers, io.notifications, io.broadcasting, io.middleware; hooks register_listeners / register_observers

Relacionado con ADR 010 (módulo IO), ADR 006 (MCP), ADR 017 (datos por plugin) y ADR 008 (events emitidos por plugins de dominio; ejemplo booking.confirmed).

Contexto

Los plugins Independientes no deben importarse entre sí. Hoy la integración entre dominios se plantea por REST (el llamador conoce la ruta del otro plugin). Hace falta un relevo por contrato nombrado: un plugin emite un hecho de dominio; otros reaccionan solo si están activos; cero receptores no rompe la plataforma.

Además hay que separar responsabilidades que suelen mezclarse:

  • borde HTTP (quién entra),
  • ciclo de vida de los datos del propio plugin,
  • hecho público de dominio,
  • entrega a humanos o sistemas externos,
  • push en tiempo real a la UI.

Decisión

Seis conceptos viven en cortex_framework.io (capa Dependiente, siempre cargada). Los plugins Independientes solo registran listeners/observers y emiten events desde sus servicios o observers.

Concepto Pregunta que responde Alcance
Middleware ¿Esta petición puede entrar y con qué contexto? Borde HTTP (apirest / guards)
Observers ¿Qué hacer en este plugin cuando mutan sus datos? Intra-plugin (ADR 017)
Events ¿Qué pasó en el dominio para que otros reaccionen? Contrato público de plataforma
Listeners ¿Quién reacciona a un event sin conocer al emisor? Lado receptor de events
Notifications ¿A quién avisar y por qué canal? Entrega multi-canal
Broadcasting ¿Qué clientes en vivo deben enterarse? Push UI (fachada sobre io.ws)

Listeners no son un bus aparte: son el lado receptor de events.

Qué hace / qué no hace

Concepto Hace No hace
Middleware Auth, rate limit, plugin habilitado, errores de transporte Reglas de negocio de un dominio
Observers Side-effects locales tras crear/actualizar/borrar entidades propias; suelen emitir un event Llamar a otro plugin ni observar modelos ajenos
Events Publicar hecho tipado (booking.confirmed) con payload estable Enviar email ni empujar al browser por sí solos
Listeners Reaccionar a un event (p. ej. crear cobro en payments) Observar tablas de otro plugin
Notifications Entregar aviso por canales (mail, webhook externo, registro) Sustituir lógica de negocio entre plugins
Broadcasting Publicar a clientes conectados vía WebSocket Lectura del modelo de otro plugin

Flujo de referencia

Fuente: docs/diagrams/adr/021-io-dominio-events-01.mermaid.

flowchart TB
  subgraph edge [Borde HTTP]
    MW[middleware]
  end
  subgraph pluginOwner [Plugin dueño de datos]
    Handler[Handler REST o MCP]
    Model[Persistencia propia]
    OBS[observers]
  end
  subgraph platform [cortex_framework.io]
    EV[events]
    NT[notifications]
    BC[broadcasting]
  end
  subgraph others [Otros plugins activos]
    L[listeners]
  end
  HTTP[Request] --> MW --> Handler
  Handler --> Model
  Model --> OBS
  OBS -->|emite contrato nombrado| EV
  Handler -->|emit opcional| EV
  EV --> L
  EV --> NT
  EV --> BC
  BC --> WS[io.ws]
  WS --> UI[Panel React]

Leyenda: Request seguro, mutación local, hecho público, reacciones desacopladas y entrega. Estado: Implementado (beta).

Reglas

  1. Cero listeners = no-op. Emitir un event nunca falla porque no haya suscriptores.
  2. Observer no conoce otros plugins. Como máximo emite un event de plataforma.
  3. Listener no observa modelos ajenos. Solo escucha events (o lee por REST si el contrato lo exige y el plugin destino está activo).
  4. Notification ≠ event. El event es el hecho; la notification es el aviso.
  5. Broadcasting se apoya en io.ws. No introduce un transporte paralelo en la fase de diseño.
  6. Plugins activos únicamente. Solo plugins habilitados en /control/plugins registran listeners/observers en bootstrap.
  7. Sin imports cruzados entre paquetes cortex_plugin_*.

SPI implementado

Hooks de bootstrap (mismo patrón que register_mcp_tools):

Hook Rol
register_listeners(registry) EventListenerRegistrar.listen(event_name, handler)
register_observers(registry) ObserverRegistrar.observe(entity_key, lifecycle, handler)

Runtime:

  • platform.event_dispatcher.emit(name, payload) / app.state.event_dispatcher
  • platform.observer_registry.notify(entity_key, lifecycle, entity)
  • platform.notification_manager.send(notifiable, notification)
  • platform.broadcaster.broadcast(channel, event_type, payload)
  • platform.middleware_pipeline aplicado en create_app

Paquetes: framework/cortex_framework/io/{events,observers,notifications,broadcasting,middleware}/.

Relación con transports existentes

Transport (ADR 010) Relación con este ADR
apirest Punto de entrada; middleware del borde
mcp / ws Fachadas a agentes y UI; no sustituyen events entre plugins
webhooks Canal externo de notifications (URLs fuera de Cortex)
broadcasting Fachada de dominio sobre ws

Ejemplo de dominio

Tras confirmar una reserva (booking):

  1. Middleware valida la petición.
  2. El servicio persiste la reserva.
  3. Un observer de booking emite booking.confirmed.
  4. Un listener de payments (si el plugin está activo) inicia el cobro.
  5. Una notification avisa al cliente por el canal configurado.
  6. Broadcasting actualiza la agenda en el panel vía WebSocket.

Los plugins de dominio adoptan events de forma incremental. Implementado: booking.confirmed en plugin booking; listener booking.confirmed en payments. Pendiente: listeners en discounts y otros consumidores del grafo Reservas. Spec booking: plugins/booking/design.md. Catálogo: Plugins > booking.

Fuera de alcance de este ADR

  • Bus de datos y hooks de puertos entre plugins (entrada/salida de datos tipo cable explícito entre capacidades). Se documentará en una decisión posterior.
  • Colas asíncronas obligatorias (fase 1 del diseño asume dispatch síncrono; colas se evalúan con ADR 009).

Consecuencias

  • Positivas: vocabulario estable; relevo sin acoplar paquetes; ownership de datos (ADR 017).
  • Negativas: plugins deben adoptar emit/notify explícitamente; canales de notification más allá de log pendientes (mail, webhooks).

Referencias

  • Guía: Eventos de dominio
  • ADR 010
  • Diagramas: docs/diagrams/adr/021-io-dominio-events-01.mermaid
  • framework/cortex_framework/io/