Event Bus und Observer-Pattern in Rust: Implementierung und Beispiele
Ein Event Bus ist ein Architekturmuster, das das Publish-Subscribe-Prinzip (Verleger-Abonnent) umsetzt. In Rust ist dieses Muster besonders nützlich, um lose gekoppelte Systeme zu bauen, bei denen Komponenten über Ereignisse kommunizieren, ohne sich direkt zu kennen. In diesem Artikel zeigen wir, wie du einen eigenen Event Bus mit dem Observer-Pattern erstellst, und betrachten fertige Lösungen aus dem Rust-Ökosystem.
Observer-Pattern: Grundlagen
Das Observer-Pattern definiert eine Eins-zu-Viele-Beziehung zwischen Objekten. Wenn ein Objekt (der Verleger) seinen Zustand ändert, werden alle abhängigen Objekte (die Abonnenten) automatisch benachrichtigt. In Rust wird dies oft über Traits und dynamische Dispatch realisiert.
Basisimplementierung des Observer-Patterns in Rust
use std::collections::HashMap;
// Trait für den Beobachter
trait Observer {
fn update(&self, event: &str);
}
// Struktur des Verlegers
struct EventManager {
observers: Vec<Box<dyn Observer>>,
}
impl EventManager {
fn new() -> Self {
EventManager {
observers: Vec::new(),
}
}
fn subscribe(&mut self, observer: Box<dyn Observer>) {
self.observers.push(observer);
}
fn notify(&self, event: &str) {
for observer in &self.observers {
observer.update(event);
}
}
}
// Konkreter Beobachter
struct Logger;
impl Observer for Logger {
fn update(&self, event: &str) {
println!("[Logger] Ereignis empfangen: {}", event);
}
}
fn main() {
let mut manager = EventManager::new();
manager.subscribe(Box::new(Logger));
manager.notify("user.login");
}
Event Bus: Erweiterte Version des Observer-Patterns
Ein Event Bus ist eine zentrale Schnittstelle, die Ereignisse nach Typen routet. Im Gegensatz zum einfachen Observer werden hier verschiedene Ereignistypen und gefilterte Abonnements unterstützt.
Implementierung eines Event Bus mit Ereignistypen
use std::any::Any;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
// Ereignistyp
#[derive(Clone, Debug)]
enum EventType {
UserLogin,
UserLogout,
DataUpdate,
}
// Ereignisstruktur
#[derive(Clone, Debug)]
struct Event {
event_type: EventType,
payload: String,
}
// Ereignis-Handler
type Handler = Arc<dyn Fn(Event) + Send + Sync>;
// Event Bus
struct EventBus {
handlers: HashMap<EventType, Vec<Handler>>,
}
impl EventBus {
fn new() -> Self {
EventBus {
handlers: HashMap::new(),
}
}
fn subscribe<F>(&mut self, event_type: EventType, handler: F)
where
F: Fn(Event) + 'static + Send + Sync,
{
self.handlers
.entry(event_type)
.or_insert_with(Vec::new)
.push(Arc::new(handler));
}
fn publish(&self, event: Event) {
if let Some(handlers) = self.handlers.get(&event.event_type) {
for handler in handlers {
handler(event.clone());
}
}
}
}
fn main() {
let mut bus = EventBus::new();
bus.subscribe(EventType::UserLogin, |event| {
println!("Benutzer angemeldet: {}", event.payload);
});
bus.subscribe(EventType::UserLogin, |event| {
// Ein weiterer Handler
println!("Login protokolliert: {}", event.payload);
});
bus.publish(Event {
event_type: EventType::UserLogin,
payload: "user123".to_string(),
});
}
Asynchroner Event Bus mit Tokio
In echten Anwendungen ist oft eine asynchrone Ereignisverarbeitung erforderlich. Hierfür eignet sich die Bibliothek tokio mit ihren broadcast-Kanälen hervorragend.
Verwendung von tokio::sync::broadcast
use tokio::sync::broadcast;
#[tokio::main]
async fn main() {
// Erstelle einen broadcast-Kanal mit einem Puffer von 16 Nachrichten
let (tx, mut rx1) = broadcast::channel::<String>(16);
let mut rx Auch in der Bibliothek
Konfiguration von C#-Anwendungen (.NET): Bibliotheken und Tools
Autorisierung und Authentifizierung in Java: Vollständiger Leitfaden
Scrutor für C# (.NET): Vollständige Anleitung mit Beispielen