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¶
- Cero listeners = no-op. Emitir un event nunca falla porque no haya suscriptores.
- Observer no conoce otros plugins. Como máximo emite un event de plataforma.
- Listener no observa modelos ajenos. Solo escucha events (o lee por REST si el contrato lo exige y el plugin destino está activo).
- Notification ≠ event. El event es el hecho; la notification es el aviso.
- Broadcasting se apoya en
io.ws. No introduce un transporte paralelo en la fase de diseño. - Plugins activos únicamente. Solo plugins habilitados en
/control/pluginsregistran listeners/observers en bootstrap. - 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_dispatcherplatform.observer_registry.notify(entity_key, lifecycle, entity)platform.notification_manager.send(notifiable, notification)platform.broadcaster.broadcast(channel, event_type, payload)platform.middleware_pipelineaplicado encreate_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):
- Middleware valida la petición.
- El servicio persiste la reserva.
- Un observer de
bookingemitebooking.confirmed. - Un listener de
payments(si el plugin está activo) inicia el cobro. - Una notification avisa al cliente por el canal configurado.
- 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
logpendientes (mail, webhooks).
Referencias¶
- Guía: Eventos de dominio
- ADR 010
- Diagramas:
docs/diagrams/adr/021-io-dominio-events-01.mermaid framework/cortex_framework/io/