Event Bus et le pattern Observer en Rust : construire une architecture événementielle flexible
Dans le développement logiciel moderne, il est souvent nécessaire d'avoir un couplage faible entre les composants. Le pattern Observer et son implémentation pratique — l'Event Bus (bus d'événements) — permettent d'organiser une interaction efficace entre les modules sans dépendances directes. Rust, avec son système de types strict et l'accent mis sur la sécurité, offre d'excellentes possibilités pour implémenter ce pattern. Dans cet article, nous allons voir comment construire votre propre Event Bus en Rust, en utilisant les fonctionnalités intégrées du langage et les crates populaires.
Qu'est-ce que l'Event Bus et le pattern Observer ?
Le pattern Observer définit une relation "un à plusieurs" entre les objets : lorsqu'un objet (l'éditeur) change d'état, tous les objets dépendants (les abonnés) en sont automatiquement notifiés. L'Event Bus est un bus centralisé qui gère les abonnements et la distribution des événements, permettant à tout composant de publier des événements et de s'y abonner.
Principaux avantages :
- Couplage faible : les éditeurs et les abonnés ne se connaissent pas directement
- Extensibilité : il est facile d'ajouter de nouveaux abonnés sans modifier le code existant
- Testabilité : les composants peuvent être testés isolément en simulant des événements
- Asynchronisme : les événements peuvent être traités en arrière-plan
Implémentation de base d'un Event Bus en Rust
Commençons par une implémentation synchrone simple, en utilisant les fonctionnalités standard de Rust. Elle sera basée sur une HashMap, où la clé est le type d'événement et la valeur une liste de gestionnaires.
use std::collections::HashMap;use std::any::{Any, TypeId};use std::marker::PhantomData;
// Type d'événement avec un identifiant uniquepub trait Event: Any + Clone + Send + 'static { fn event_type(&self) -> TypeId { TypeId::of::<Self>() }}
// Gestionnaire d'événementpub trait EventHandler<E: Event>: Send + 'static { fn handle(&self, event: &E);}
// Le bus d'événements lui-mêmepub 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); } } } }}Exemple d'utilisation
// Définir les événements#[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 {}
// Définir les gestionnairesstruct Logger;impl EventHandler<UserLoggedIn> for Logger { fn handle(&self, event: &UserLoggedIn) { println!("[LOG] Utilisateur {} (ID: {}) connecté", event.username, event.user_id); }}
struct EmailNotifier;impl EventHandler<UserLoggedIn> for EmailNotifier { fn handle(&self, event: &UserLoggedIn) { println!("[EMAIL] Envoi d'un e-mail de bienvenue à {}", event.username); }}
fn main() { le Aussi dans la bibliothèque
Client HTTP pour Kotlin : Top bibliothèques pour travailler avec le réseau
Redis (ioredis) pour JavaScript (Node.js) : guide complet avec exemples
Autorisation et authentification en C# (.NET) — guide complet
Traitement d'images : redimensionnement et recadrage en C++ avec OpenCV