Event Bus и паттерн Observer в Rust: реализация событийной архитектуры

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Event Bus и паттерн Observer в Rust: строим гибкую событийную архитектуру

В современной разработке программного обеспечения часто возникает необходимость в слабой связности компонентов. Паттерн Observer и его практическая реализация — Event Bus (шина событий) — позволяют организовать эффективное взаимодействие между модулями без прямых зависимостей. Rust, с его строгой системой типов и акцентом на безопасность, предоставляет отличные возможности для реализации этого паттерна. В этой статье мы разберём, как построить собственный Event Bus в Rust, используя встроенные возможности языка и популярные крейты.

Что такое Event Bus и паттерн Observer?

Паттерн Observer определяет отношение "один ко многим" между объектами: когда один объект (издатель) меняет своё состояние, все зависимые объекты (подписчики) уведомляются об этом автоматически. Event Bus — это централизованная шина, которая управляет подписками и рассылкой событий, позволяя любому компоненту публиковать события и подписываться на них.

Основные преимущества:

  • Слабая связность: издатели и подписчики не знают друг о друге напрямую
  • Масштабируемость: легко добавлять новых подписчиков без изменения существующего кода
  • Тестируемость: можно изолированно тестировать компоненты, эмулируя события
  • Асинхронность: события могут обрабатываться в фоновом режиме

Базовая реализация Event Bus на Rust

Начнём с простой синхронной реализации, используя стандартные средства Rust. В основе будет лежать HashMap, где ключом выступает тип события, а значением — список обработчиков.

use std::collections::HashMap;
use std::any::{Any, TypeId};
use std::marker::PhantomData;

// Тип события с уникальным идентификатором
pub trait Event: Any + Clone + Send + 'static {
    fn event_type(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

// Обработчик события
pub trait EventHandler<E: Event>: Send + 'static {
    fn handle(&self, event: &E);
}

// Сама шина событий
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);
                }
            }
        }
    }
}

Пример использования

// Определяем события
#[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 {}

// Определяем обработчики
struct 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() {
    let mut bus = EventBus::new();
    bus.subscribe::<UserLoggedIn, Logger>(Logger);
    bus.subscribe::<UserLoggedIn, EmailNotifier>(EmailNotifier);

    let event = UserLoggedIn { user_id: 42, username: "alice".to_string() };
    bus.publish(&event);
    // Вывод:
    // [LOG] User alice (ID: 42) logged in
    // [EMAIL] Sending welcome email to alice
}

Асинхронная реализация с tokio

Для реальных приложений часто требуется асинхронная обработка событий. Используем крейт tokio и каналы broadcast:

use tokio::sync::broadcast;
use std::fmt::Debug;

#[derive(Clone, Debug)]
pub enum AppEvent {
    UserAction { user_id: u32, action: String },
    System                    

Рекомендаций