Eventos de dominio¶
Reacciona a hechos de negocio entre plugins sin importar el paquete del otro: middleware en el borde, observers en tus datos, events y listeners en la plataforma, notifications y broadcasting para avisar.
Beta
API en cortex_framework.io (events, observers, notifications, broadcasting, middleware). Decisiones: ADR 021 y ADR 010. Adopción incremental: booking emite booking.confirmed; payments escucha con listener registrado en bootstrap.
Cuándo aplica¶
- Un plugin debe reaccionar cuando otro confirma un hecho (
entity.confirmed, factura emitida, …). - Quieres side-effects locales al guardar tus entidades sin repetir código en cada endpoint.
- Necesitas separar “pasó el hecho”, “avisar a alguien” y “actualizar la UI en vivo”.
Mapa de conceptos¶
| Pieza | Pregunta | Dónde vive |
|---|---|---|
| Middleware | ¿La petición puede entrar? | Borde HTTP (apirest, guards) |
| Observers | ¿Qué hago en mi plugin al mutar mis datos? | Plugin dueño del modelo (ADR 017) |
| Events | ¿Qué hecho público ocurrió? | Contrato de plataforma (io.events) |
| Listeners | ¿Quién reacciona a ese hecho? | Plugins activos que se registran en bootstrap |
| Notifications | ¿A quién aviso y por qué canal? | Canales de entrega (io.notifications) |
| Broadcasting | ¿Qué clientes en vivo se enteran? | Fachada sobre WebSocket (io.ws) |
Listeners son el lado receptor de events, no un mecanismo aparte.
Fuente del diagrama: docs/diagrams/guias-plugins-eventos-dominio-01.mermaid.
flowchart LR
OBS[observers] -->|ciclo de vida modelo| EV[events]
EV --> L[listeners]
EV --> NT[notifications]
EV --> BC[broadcasting]
BC --> WS[io.ws]
MW[middleware borde HTTP] Leyenda: Middleware es borde HTTP (aparte). Observers emiten events; listeners, notifications y broadcasting reaccionan. Estado: Implementado (beta).
Middleware¶
Pipeline del request (auth, rate limit, plugin habilitado, errores de transporte).
- Corre antes de tu handler REST o del borde MCP.
- No emite hechos de dominio ni crea pagos.
- Piezas actuales viven junto a la API FastAPI; el diseño las agrupa bajo IO (ADR 010).
Observers¶
Gancho al ciclo de vida de tus entidades (crear, actualizar, borrar).
- Solo el plugin que posee la tabla o el store registra observers sobre esos datos.
- Sirven para lógica local (auditoría del plugin, normalización) y, sobre todo, para emitir un event de plataforma de forma centralizada.
- No llaman a otro
cortex_plugin_*ni observan modelos ajenos.
Si no usas observer, el servicio puede emitir el event a mano; el observer evita olvidarlo en cada ruta.
Events¶
Hecho de dominio público, con nombre estable y payload acordado.
- Ejemplo:
helloworld.item_createdtras crear un ítem de demo en el pluginhelloworld. - El emisor no conoce a los listeners.
- Cero listeners: el emit no falla (no-op).
- No envía email ni empuja al panel por sí solo; puede disparar notifications o broadcasting en listeners dedicados.
Listeners¶
Funciones (o handlers) que se registran en bootstrap del plugin activo y se asocian a un nombre de event.
- Solo se cargan listeners de plugins activos en el state store.
- Si el plugin consumidor no está activo, no hay listener: el emit sigue siendo válido.
- Un listener hace trabajo de su dominio, no observa la base de otro plugin.
Hook: register_listeners(registry) — registry.listen("helloworld.item_created", handler).
Notifications¶
Entrega de un aviso por uno o más canales (correo, webhook externo, registro interno, …).
- Separado del event: el event es “reserva confirmada”; la notification es “avisar al cliente”.
- Los canales pluggables viven en el framework; el plugin de dominio no implementa SMTP.
- Los webhooks hacia URLs externas (ADR 010) se modelan como canal de notification, no como bus entre plugins.
Broadcasting¶
Push en tiempo real a clientes conectados (panel React).
- Transporte base: WebSocket ya implementado (
WS /api/v1/ws). Ver MCP y WebSocket. - Broadcasting es la fachada de dominio (“publica este hecho a la UI”), no un segundo protocolo.
- Si no hay clientes conectados, no debe tumbar la operación de negocio.
Flujo ejemplo: ítem creado (plugin helloworld)¶
- Middleware valida la petición a
POST /api/v1/samples/todo. - El servicio del plugin helloworld persiste el ítem.
- Un observer (
register_observers) encreatedemitehelloworld.item_createdvíaevent_dispatcher.emit. - Un listener de otro plugin activo reacciona al hecho (p. ej. actualizar un índice).
- Una notification (
notification_manager.send) avisa si aplica. - Broadcasting (
broadcaster.broadcast) publica al canal de UI.
Si el plugin consumidor no está activo, no hay listener y el emit no falla.
Emitir desde un servicio¶
Registrar un listener¶
async def on_item_created(payload: dict) -> None:
...
def register_listeners(registry) -> None:
registry.listen("helloworld.item_created", on_item_created)
Qué no mezclar¶
| Incorrecto | Correcto |
|---|---|
| Middleware crea el side-effect del otro plugin | Listener del plugin consumidor reacciona al event |
| Observer del plugin B lee tablas del plugin A | Listener escucha el event con payload acordado |
| Event “envía el email” como única responsabilidad | Event + notification por canal |
Plugin A importa cortex_plugin_b | Event/listener o REST sin import cruzado |
| Broadcasting como forma de que un plugin lea el modelo de otro | Event con payload o REST al plugin activo |
Integración en paralelo¶
- Events/listeners para relevo desacoplado (esta guía).
- REST sin imports cruzados cuando el llamador conoce la ruta (contexto del proyecto).
- MCP para orquestación por agentes (MCP y WebSocket).
Fuera de esta guía¶
- Bus de datos y hooks de puertos entre capacidades de plugins (entrada/salida de datos como cables explícitos). Vendrán en una decisión posterior; no forman parte de este diseño.
- Colas asíncronas obligatorias (se evaluarán con Redis / ADR 009).
Errores frecuentes¶
| Síntoma | Causa probable |
|---|---|
| Listener no corre | Plugin no exporta register_listeners o no está activo en /control/plugins |
| Plugin B importa plugin A | Prohibido; usa event/listener o REST |
| Observer del plugin B sobre modelo del plugin A | Ownership incorrecto (ADR 017) |
| Confundir notification con event | Event = hecho; notification = aviso |
Siguiente paso¶
- Ciclo de vida — hooks de bootstrap (incluidos los previstos).
- Persistencia — datos por plugin.
- ADR 021 — decisión completa.
- ADR 010 — mapa del módulo IO.