Event Bus y el patrón Observer en JavaScript (Node.js)
En el desarrollo moderno con Node.js, a menudo surge la necesidad de organizar la interacción entre diferentes componentes de una aplicación sin crear vínculos rígidos entre ellos. El patrón Observer (Observador) y su implementación más potente — Event Bus (Bus de eventos) — ofrecen una solución elegante para este problema. En este artículo, analizaremos en detalle cómo implementar estos patrones en JavaScript puro en el entorno Node.js, examinaremos sus ventajas y escenarios de uso típicos.
¿Qué es el patrón Observer?
El patrón Observer define una relación de "uno a muchos" entre objetos: cuando un objeto (sujeto) cambia su estado, todos los objetos que dependen de él (observadores) son notificados automáticamente. En el contexto de Node.js, este patrón es la base del módulo incorporado events, que proporciona la clase EventEmitter.
Event Bus: extensión del concepto
Event Bus es un bus de eventos centralizado que permite a cualquier componente de la aplicación enviar y recibir mensajes sin conocerse directamente entre sí. Esto es especialmente útil en arquitecturas de microservicios, aplicaciones complejas con estructura modular o al crear sistemas de plugins.
Implementación de Event Bus en Node.js
Consideremos la creación de nuestro propio Event Bus desde cero. Nuestra clase admitirá los métodos principales: suscripción a eventos (on), suscripción única (once), eliminación de suscriptores (off) y emisión de eventos (emit).
class EventBus { constructor() { this.events = {}; }
on(event, listener) { if (!this.events[event]) { this.events[event] = []; } this.events[event].push(listener); return () => this.off(event, listener); }
once(event, listener) { const wrapper = (...args) => { listener(...args); this.off(event, wrapper); }; this.on(event, wrapper); return () => this.off(event, wrapper); }
off(event, listener) { if (!this.events[event]) return; this.events[event] = this.events[event].filter(l => l !== listener); }
emit(event, ...args) { if (!this.events[event]) return false; this.events[event].forEach(listener => listener(...args)); return true; }}
// Ejemplo de usoconst bus = new EventBus();
const unsubscribe = bus.on('user:login', (username) => { console.log(`Usuario ${username} ha iniciado sesión`);});
bus.once('app:start', () => { console.log('Aplicación iniciada (notificación única)');});
// Emisión de eventosbus.emit('app:start'); // "Aplicación iniciada (notificación única)"bus.emit('user:login', 'Ana'); // "Usuario Ana ha iniciado sesión"
// Cancelar suscripciónunsubscribe();bus.emit('user:login', 'Iván'); // No ocurre nadaUso del EventEmitter incorporado de Node.js
Node.js proporciona una implementación lista de EventEmitter en el módulo events. Sin embargo, un Event Bus creado sobre esta base permite gestionar de forma centralizada todos los eventos de la aplicación.
const EventEmitter = require('events');
class AppEventBus extends EventEmitter { constructor() { super(); this.setMaxListeners(50); // Aumentamos el límite de suscriptores }
// Añadimos registro para depuración emit(event, ...args) { console.log(`[EventBus] Evento: ${event}`); return super.emit(event, ...args); }}
const appBus = new AppEventBus();
// Suscripción a eventosappBus.on('order:created', (order) => { console.log(`Pedido #${order.id} creado. Enviando notificación.`);});
appBus.on('order:created', (order) => { console.log(`Pedido #${order.id} creado. Actualizando almacén.`);});
// EmisiónappBus.emit('o También en la biblioteca
Registro en Go (Golang): mejores bibliotecas y soluciones 2024
TypeStack/routing-controllers para TypeScript: guía completa con ejemplos
Registro en Kotlin: mejores bibliotecas y herramientas para el desarrollo
Frameworks CLI para Kotlin: creación de aplicaciones de consola