Event Bus y el patrón Observer en Rust: construyendo una arquitectura de eventos flexible
En el desarrollo de software moderno, a menudo surge la necesidad de un bajo acoplamiento entre componentes. El patrón Observer y su implementación práctica — Event Bus (bus de eventos) — permiten organizar una interacción eficiente entre módulos sin dependencias directas. Rust, con su estricto sistema de tipos y su enfoque en la seguridad, ofrece excelentes oportunidades para implementar este patrón. En este artículo, analizaremos cómo construir su propio Event Bus en Rust, utilizando las capacidades integradas del lenguaje y crates populares.
¿Qué es Event Bus y el patrón Observer?
El patrón Observer define una relación de "uno a muchos" entre objetos: cuando un objeto (editor) cambia su estado, todos los objetos dependientes (suscriptores) son notificados automáticamente. Event Bus es un bus centralizado que gestiona las suscripciones y la distribución de eventos, permitiendo que cualquier componente publique eventos y se suscriba a ellos.
Principales ventajas:
- Bajo acoplamiento: los editores y suscriptores no se conocen directamente entre sí
- Escalabilidad: es fácil agregar nuevos suscriptores sin modificar el código existente
- Capacidad de prueba: los componentes se pueden probar de forma aislada, simulando eventos
- Asincronía: los eventos se pueden procesar en segundo plano
Implementación básica de Event Bus en Rust
Comencemos con una implementación síncrona simple, utilizando las herramientas estándar de Rust. La base será un HashMap, donde la clave es el tipo de evento y el valor es una lista de manejadores.
use std::collections::HashMap;use std::any::{Any, TypeId};use std::marker::PhantomData;
// Tipo de evento con identificador únicopub trait Event: Any + Clone + Send + 'static { fn event_type(&self) -> TypeId { TypeId::of::<Self>() }}
// Manejador de eventospub trait EventHandler<E: Event>: Send + 'static { fn handle(&self, event: &E);}
// El bus de eventos en sípub struct EventBus { handlers: HashMap<TypeId, Vec<Box<dyn Any>>>,}
impl EventBus { pub fn new() -> Self { EventBus { handlers: HashMap::new(), } }
pub fn subscribe<E: Event, H: EventHandler<E>>(&mut self, handler: H) { let type_id = TypeId::of::<E>(); let boxed_handler = Box::new(handler) as Box<dyn Any>; self.handlers.entry(type_id).or_insert(Vec::new()).push(boxed_handler); }
pub fn publish<E: Event>(&self, event: &E) { let type_id = event.event_type(); if let Some(handlers) = self.handlers.get(&type_id) { for handler in handlers { if let Some(typed_handler) = handler.downcast_ref::<Box<dyn EventHandler<E>>>() { typed_handler.handle(event); } } } }}Ejemplo de uso
// Definimos eventos#[derive(Clone)]struct UserLoggedIn { user_id: u32, username: String,}
impl Event for UserLoggedIn {}
#[derive(Clone)]struct DataUpdated { table: String, record_id: u64,}
impl Event for DataUpdated {}
// Definimos manejadoresstruct Logger;impl EventHandler<UserLoggedIn> for Logger { fn handle(&self, event: &UserLoggedIn) { println!("[LOG] User {} (ID: {}) logged in", event.username, event.user_id); }}
struct EmailNotifier;impl EventHandler<UserLoggedIn> for EmailNotifier { fn handle(&self, event: &UserLoggedIn) { println!("[EMAIL] Sending welcome email to {}", event.username); }}
fn main() { le