[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-21-event-driven-rust-pattern-shiny":3},{"article":4,"author":55},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":16,"meta_description":17,"focus_keyword":18,"og_image":19,"canonical_url":19,"robots_meta":20,"created_at":15,"updated_at":15,"tags":21,"category_name":35,"related_articles":36},"d0000000-0000-0000-0000-000000000221","a0000000-0000-0000-0000-000000000016","Deep EVM #21: Event-driven архитектура на Rust — паттерн шины для реалтайм-систем","deep-evm-21-event-driven-rust-pattern-shiny","Проектирование event-driven систем на Rust: паттерн шины событий, tokio broadcast, и архитектура для реалтайм-обработки блокчейн-данных.","## Зачем event-driven архитектура\n\nВ блокчейн-инфраструктуре события — это основа всего. Новый блок, транзакция в мемпуле, изменение цены в пуле ликвидности — всё это события, на которые ваша система должна реагировать в реальном времени. Традиционная архитектура запрос-ответ здесь не работает: вы не можете поллить блокчейн каждую миллисекунду.\n\nEvent-driven архитектура (EDA) решает эту проблему через асинхронную обработку потока событий. Компоненты системы не вызывают друг друга напрямую — они публикуют и подписываются на события через шину.\n\n## Паттерн шины событий в Rust\n\nЦентральный элемент EDA — шина событий (event bus). В Rust мы можем реализовать её через `tokio::sync::broadcast`:\n\n```rust\nuse tokio::sync::broadcast;\nuse serde::{Serialize, Deserialize};\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\nenum Event {\n    NewBlock { number: u64, hash: String },\n    PendingTx { hash: String, from: String, to: String },\n    PriceUpdate { pair: String, price: f64 },\n    LiquidationRisk { address: String, health_factor: f64 },\n}\n\nstruct EventBus {\n    sender: broadcast::Sender\u003CEvent>,\n}\n\nimpl EventBus {\n    fn new(capacity: usize) -> Self {\n        let (sender, _) = broadcast::channel(capacity);\n        Self { sender }\n    }\n\n    fn publish(&self, event: Event) {\n        \u002F\u002F Игнорируем ошибку, если нет подписчиков\n        let _ = self.sender.send(event);\n    }\n\n    fn subscribe(&self) -> broadcast::Receiver\u003CEvent> {\n        self.sender.subscribe()\n    }\n}\n```\n\n## Архитектура компонентов\n\nСистема состоит из трёх типов компонентов:\n\n### Producers (источники событий)\n\n```rust\nasync fn block_watcher(\n    bus: Arc\u003CEventBus>,\n    provider: Arc\u003CProvider>,\n) {\n    let mut stream = provider.subscribe_blocks().await.unwrap();\n\n    while let Some(block) = stream.next().await {\n        bus.publish(Event::NewBlock {\n            number: block.number.unwrap().as_u64(),\n            hash: format!(\"{:?}\", block.hash.unwrap()),\n        });\n    }\n}\n```\n\n### Consumers (обработчики событий)\n\n```rust\nasync fn arbitrage_detector(\n    bus: Arc\u003CEventBus>,\n    strategy: Arc\u003CStrategy>,\n) {\n    let mut rx = bus.subscribe();\n\n    while let Ok(event) = rx.recv().await {\n        match event {\n            Event::PriceUpdate { pair, price } => {\n                if let Some(opportunity) = strategy.check(&pair, price) {\n                    bus.publish(Event::ArbitrageFound(opportunity));\n                }\n            }\n            _ => {} \u002F\u002F Игнорируем нерелевантные события\n        }\n    }\n}\n```\n\n### Hybrid (и источник, и обработчик)\n\nКомпонент может подписываться на одни события и публиковать другие, создавая цепочки обработки.\n\n## Управление backpressure\n\nОдна из ключевых проблем EDA — backpressure. Если consumer не успевает обрабатывать события, канал переполняется.\n\n`broadcast` в tokio при переполнении отбрасывает старые сообщения и возвращает `RecvError::Lagged`. Для критичных consumer'ов используйте буферизированные каналы:\n\n```rust\nasync fn buffered_consumer(\n    bus: Arc\u003CEventBus>,\n) {\n    let mut rx = bus.subscribe();\n    let (tx, mut buffered_rx) = tokio::sync::mpsc::channel(10_000);\n\n    \u002F\u002F Буферизация в отдельной задаче\n    tokio::spawn(async move {\n        while let Ok(event) = rx.recv().await {\n            if tx.send(event).await.is_err() {\n                break;\n            }\n        }\n    });\n\n    \u002F\u002F Обработка из буфера\n    while let Some(event) = buffered_rx.recv().await {\n        process(event).await;\n    }\n}\n```\n\n## Типизированные каналы с enum dispatch\n\nВместо одного enum Event для всех событий можно использовать типизированные каналы через трейт-объекты:\n\n```rust\ntrait EventHandler: Send + Sync {\n    fn event_type(&self) -> &'static str;\n    fn handle(&self, payload: &[u8]) -> Result\u003C(), Error>;\n}\n\nstruct TypedBus {\n    handlers: DashMap\u003CString, Vec\u003CArc\u003Cdyn EventHandler>>>,\n}\n```\n\nНо на практике enum dispatch проще, быстрее (нет динамической диспетчеризации) и достаточен для большинства систем.\n\n## Персистентность событий\n\nДля отказоустойчивости события нужно сохранять. Варианты:\n\n- **PostgreSQL** с LISTEN\u002FNOTIFY для простых случаев\n- **Redis Streams** для высокой пропускной способности\n- **Apache Kafka** для корпоративных систем\n- **NATS JetStream** для облачных систем\n\nПример с Redis Streams:\n\n```rust\nasync fn persist_event(\n    redis: &mut Connection,\n    event: &Event,\n) -> Result\u003CString> {\n    let payload = serde_json::to_string(event)?;\n    let id: String = redis::cmd(\"XADD\")\n        .arg(\"events\")\n        .arg(\"MAXLEN\")\n        .arg(\"~\")\n        .arg(100_000)\n        .arg(\"*\")\n        .arg(\"data\")\n        .arg(&payload)\n        .query_async(redis)\n        .await?;\n    Ok(id)\n}\n```\n\n## Мониторинг и метрики\n\nДля EDA критичны следующие метрики:\n\n- **Event throughput** — количество событий в секунду\n- **Processing latency** — время от публикации до обработки\n- **Queue depth** — размер очередей consumer'ов\n- **Error rate** — процент ошибок обработки\n\nИспользуйте `metrics` crate для экспорта в Prometheus:\n\n```rust\nmetrics::counter!(\"events_published_total\", \"type\" => \"NewBlock\").increment(1);\nmetrics::histogram!(\"event_processing_duration_seconds\").record(elapsed);\n```\n\n## Заключение\n\nEvent-driven архитектура на Rust — это мощный паттерн для реалтайм-систем, особенно в блокчейн-инфраструктуре. Комбинация tokio broadcast для in-process коммуникации и Redis Streams\u002FNATS для распределённой обработки обеспечивает масштабируемость, отказоустойчивость и низкую латентность.","\u003Ch2 id=\"event-driven\">Зачем event-driven архитектура\u003C\u002Fh2>\n\u003Cp>В блокчейн-инфраструктуре события — это основа всего. Новый блок, транзакция в мемпуле, изменение цены в пуле ликвидности — всё это события, на которые ваша система должна реагировать в реальном времени. Традиционная архитектура запрос-ответ здесь не работает: вы не можете поллить блокчейн каждую миллисекунду.\u003C\u002Fp>\n\u003Cp>Event-driven архитектура (EDA) решает эту проблему через асинхронную обработку потока событий. Компоненты системы не вызывают друг друга напрямую — они публикуют и подписываются на события через шину.\u003C\u002Fp>\n\u003Ch2 id=\"rust\">Паттерн шины событий в Rust\u003C\u002Fh2>\n\u003Cp>Центральный элемент EDA — шина событий (event bus). В Rust мы можем реализовать её через \u003Ccode>tokio::sync::broadcast\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use tokio::sync::broadcast;\nuse serde::{Serialize, Deserialize};\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\nenum Event {\n    NewBlock { number: u64, hash: String },\n    PendingTx { hash: String, from: String, to: String },\n    PriceUpdate { pair: String, price: f64 },\n    LiquidationRisk { address: String, health_factor: f64 },\n}\n\nstruct EventBus {\n    sender: broadcast::Sender&lt;Event&gt;,\n}\n\nimpl EventBus {\n    fn new(capacity: usize) -&gt; Self {\n        let (sender, _) = broadcast::channel(capacity);\n        Self { sender }\n    }\n\n    fn publish(&amp;self, event: Event) {\n        \u002F\u002F Игнорируем ошибку, если нет подписчиков\n        let _ = self.sender.send(event);\n    }\n\n    fn subscribe(&amp;self) -&gt; broadcast::Receiver&lt;Event&gt; {\n        self.sender.subscribe()\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"\">Архитектура компонентов\u003C\u002Fh2>\n\u003Cp>Система состоит из трёх типов компонентов:\u003C\u002Fp>\n\u003Ch3>Producers (источники событий)\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-rust\">async fn block_watcher(\n    bus: Arc&lt;EventBus&gt;,\n    provider: Arc&lt;Provider&gt;,\n) {\n    let mut stream = provider.subscribe_blocks().await.unwrap();\n\n    while let Some(block) = stream.next().await {\n        bus.publish(Event::NewBlock {\n            number: block.number.unwrap().as_u64(),\n            hash: format!(\"{:?}\", block.hash.unwrap()),\n        });\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Consumers (обработчики событий)\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-rust\">async fn arbitrage_detector(\n    bus: Arc&lt;EventBus&gt;,\n    strategy: Arc&lt;Strategy&gt;,\n) {\n    let mut rx = bus.subscribe();\n\n    while let Ok(event) = rx.recv().await {\n        match event {\n            Event::PriceUpdate { pair, price } =&gt; {\n                if let Some(opportunity) = strategy.check(&amp;pair, price) {\n                    bus.publish(Event::ArbitrageFound(opportunity));\n                }\n            }\n            _ =&gt; {} \u002F\u002F Игнорируем нерелевантные события\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Hybrid (и источник, и обработчик)\u003C\u002Fh3>\n\u003Cp>Компонент может подписываться на одни события и публиковать другие, создавая цепочки обработки.\u003C\u002Fp>\n\u003Ch2 id=\"backpressure\">Управление backpressure\u003C\u002Fh2>\n\u003Cp>Одна из ключевых проблем EDA — backpressure. Если consumer не успевает обрабатывать события, канал переполняется.\u003C\u002Fp>\n\u003Cp>\u003Ccode>broadcast\u003C\u002Fcode> в tokio при переполнении отбрасывает старые сообщения и возвращает \u003Ccode>RecvError::Lagged\u003C\u002Fcode>. Для критичных consumer’ов используйте буферизированные каналы:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">async fn buffered_consumer(\n    bus: Arc&lt;EventBus&gt;,\n) {\n    let mut rx = bus.subscribe();\n    let (tx, mut buffered_rx) = tokio::sync::mpsc::channel(10_000);\n\n    \u002F\u002F Буферизация в отдельной задаче\n    tokio::spawn(async move {\n        while let Ok(event) = rx.recv().await {\n            if tx.send(event).await.is_err() {\n                break;\n            }\n        }\n    });\n\n    \u002F\u002F Обработка из буфера\n    while let Some(event) = buffered_rx.recv().await {\n        process(event).await;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"enum-dispatch\">Типизированные каналы с enum dispatch\u003C\u002Fh2>\n\u003Cp>Вместо одного enum Event для всех событий можно использовать типизированные каналы через трейт-объекты:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">trait EventHandler: Send + Sync {\n    fn event_type(&amp;self) -&gt; &amp;'static str;\n    fn handle(&amp;self, payload: &amp;[u8]) -&gt; Result&lt;(), Error&gt;;\n}\n\nstruct TypedBus {\n    handlers: DashMap&lt;String, Vec&lt;Arc&lt;dyn EventHandler&gt;&gt;&gt;,\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Но на практике enum dispatch проще, быстрее (нет динамической диспетчеризации) и достаточен для большинства систем.\u003C\u002Fp>\n\u003Ch2 id=\"\">Персистентность событий\u003C\u002Fh2>\n\u003Cp>Для отказоустойчивости события нужно сохранять. Варианты:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>PostgreSQL\u003C\u002Fstrong> с LISTEN\u002FNOTIFY для простых случаев\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Redis Streams\u003C\u002Fstrong> для высокой пропускной способности\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Apache Kafka\u003C\u002Fstrong> для корпоративных систем\u003C\u002Fli>\n\u003Cli>\u003Cstrong>NATS JetStream\u003C\u002Fstrong> для облачных систем\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Пример с Redis Streams:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">async fn persist_event(\n    redis: &amp;mut Connection,\n    event: &amp;Event,\n) -&gt; Result&lt;String&gt; {\n    let payload = serde_json::to_string(event)?;\n    let id: String = redis::cmd(\"XADD\")\n        .arg(\"events\")\n        .arg(\"MAXLEN\")\n        .arg(\"~\")\n        .arg(100_000)\n        .arg(\"*\")\n        .arg(\"data\")\n        .arg(&amp;payload)\n        .query_async(redis)\n        .await?;\n    Ok(id)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"\">Мониторинг и метрики\u003C\u002Fh2>\n\u003Cp>Для EDA критичны следующие метрики:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Event throughput\u003C\u002Fstrong> — количество событий в секунду\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Processing latency\u003C\u002Fstrong> — время от публикации до обработки\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Queue depth\u003C\u002Fstrong> — размер очередей consumer’ов\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Error rate\u003C\u002Fstrong> — процент ошибок обработки\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Используйте \u003Ccode>metrics\u003C\u002Fcode> crate для экспорта в Prometheus:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">metrics::counter!(\"events_published_total\", \"type\" =&gt; \"NewBlock\").increment(1);\nmetrics::histogram!(\"event_processing_duration_seconds\").record(elapsed);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"\">Заключение\u003C\u002Fh2>\n\u003Cp>Event-driven архитектура на Rust — это мощный паттерн для реалтайм-систем, особенно в блокчейн-инфраструктуре. Комбинация tokio broadcast для in-process коммуникации и Redis Streams\u002FNATS для распределённой обработки обеспечивает масштабируемость, отказоустойчивость и низкую латентность.\u003C\u002Fp>\n","ru","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:23.924135Z","Event-driven архитектура на Rust — паттерн шины для реалтайм-систем","Проектирование event-driven систем на Rust с tokio broadcast и паттерном шины событий для блокчейн-инфраструктуры.","event-driven архитектура rust",null,"index, follow",[22,27,31],{"id":23,"name":24,"slug":25,"created_at":26},"c0000000-0000-0000-0000-000000000016","EVM","evm","2026-03-28T10:44:21.513630Z",{"id":28,"name":29,"slug":30,"created_at":26},"c0000000-0000-0000-0000-000000000022","Performance","performance",{"id":32,"name":33,"slug":34,"created_at":26},"c0000000-0000-0000-0000-000000000001","Rust","rust","Инженерия",[37,43,49],{"id":38,"title":39,"slug":40,"excerpt":41,"locale":12,"category_name":35,"published_at":42},"d0200000-0000-0000-0000-000000000013","Почему Бали становится хабом импакт-технологий Юго-Восточной Азии в 2026 году","pochemu-bali-stanovitsya-khabom-impakt-tekhnologiy-2026","Бали занимает 16-е место среди стартап-экосистем Юго-Восточной Азии. Растущая концентрация Web3-разработчиков, ИИ-стартапов в области устойчивого развития и компаний в сфере эко-тревел-технологий формирует нишу столицы импакт-технологий региона.","2026-03-28T10:44:37.953039Z",{"id":44,"title":45,"slug":46,"excerpt":47,"locale":12,"category_name":35,"published_at":48},"d0200000-0000-0000-0000-000000000012","Защита данных в ASEAN: чек-лист разработчика для мультистранового комплаенса","zashchita-dannykh-asean-chek-list-razrabotchika-komplaens","Семь стран ASEAN имеют собственные законы о защите данных с разными моделями согласия, требованиями к локализации и штрафами. Практический чек-лист для разработчиков мультистрановых приложений.","2026-03-28T10:44:37.944001Z",{"id":50,"title":51,"slug":52,"excerpt":53,"locale":12,"category_name":35,"published_at":54},"d0200000-0000-0000-0000-000000000011","Цифровая трансформация Индонезии на $29 миллиардов: возможности для софтверных компаний","tsifrovaya-transformatsiya-indonezii-29-milliardov-vozmozhnosti-dlya-kompaniy","Рынок IT-услуг Индонезии вырастет с $24,37 млрд в 2025 году до $29,03 млрд в 2026 году. Облачная инфраструктура, искусственный интеллект, электронная коммерция и дата-центры обеспечивают самый быстрый рост в Юго-Восточной Азии.","2026-03-28T10:44:37.917095Z",{"id":13,"name":56,"slug":57,"bio":58,"photo_url":19,"linkedin":19,"role":59,"created_at":60,"updated_at":60},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]