Skip to main content

hyli_bus/
lib.rs

1//! # hyli-bus — Modular Monolith with Microservice Advantages
2//!
3//! `hyli-bus` is an async, in-process message bus that lets you structure your application
4//! as a set of loosely-coupled **modules** (think micro-services) while keeping them all in
5//! a single binary (think monolith).
6//!
7//! ## Design goals
8//!
9//! | Goal | Approach |
10//! |------|----------|
11//! | Clear module boundaries | Each module declares its message contract with [`module_bus_client!`] |
12//! | Simple operations | Everything runs in one binary — no network, no service discovery |
13//! | Compile-time safety | Rust's type system enforces that senders and receivers agree on types |
14//! | No serialisation overhead | Messages are cloned in-process, not marshalled to bytes |
15//! | Easy testing | Spin up only the module(s) under test and inject typed events directly |
16//!
17//! ## Quick start
18//!
19//! ### 1. Define your message types
20//!
21//! Any type that implements [`BusMessage`] can travel on the bus.
22//!
23//! ```rust,ignore
24//! #[derive(Clone)]
25//! struct OrderPlaced { order_id: u64 }
26//!
27//! #[derive(Clone)]
28//! struct QueryNextBatch;
29//!
30//! #[derive(Clone)]
31//! struct Batch(Vec<u64>);
32//!
33//! impl hyli_bus::BusMessage for OrderPlaced {}
34//! impl hyli_bus::BusMessage for QueryNextBatch {}
35//! impl hyli_bus::BusMessage for Batch {}
36//! ```
37//!
38//! ### 2. Declare a module's contract
39//!
40//! [`module_bus_client!`] generates a strongly-typed struct that owns exactly the
41//! senders and receivers declared — nothing more, nothing less.
42//!
43//! ```rust,ignore
44//! use hyli_bus::module_bus_client;
45//!
46//! module_bus_client! {
47//!     struct ProcessorBusClient {
48//!         sender(OrderPlaced),          // events this module emits
49//!         receiver(OrderPlaced),        // events this module consumes
50//!         query(QueryNextBatch, Batch), // synchronous request/response
51//!     }
52//! }
53//! ```
54//!
55//! `ShutdownModule` and `PersistModule` receivers are added automatically by the macro.
56//!
57//! ### 3. Implement the module
58//!
59//! ```rust,ignore
60//! use hyli_bus::{Module, SharedMessageBus, module_handle_messages};
61//!
62//! struct Processor {
63//!     bus: ProcessorBusClient,
64//!     // ... your state
65//! }
66//!
67//! impl Module for Processor {
68//!     type Context = (); // build-time configuration
69//!
70//!     async fn build(bus: SharedMessageBus, _ctx: ()) -> anyhow::Result<Self> {
71//!         Ok(Self { bus: ProcessorBusClient::new_from_bus(bus).await })
72//!     }
73//!
74//!     async fn run(&mut self) -> anyhow::Result<()> {
75//!         module_handle_messages! {
76//!             on_self self,
77//!
78//!             listen<OrderPlaced> ev => { /* handle event */ }
79//!
80//!             command_response<QueryNextBatch, Batch> q => {
81//!                 Ok(Batch(vec![]))
82//!             }
83//!         }
84//!         Ok(())
85//!     }
86//! }
87//! ```
88//!
89//! ### 4. Wire it all together
90//!
91//! ```rust,ignore
92//! use hyli_bus::{SharedMessageBus, ModulesHandler, ModulesHandlerOptions};
93//!
94//! #[tokio::main]
95//! async fn main() -> anyhow::Result<()> {
96//!     let bus = SharedMessageBus::new();
97//!     let mut handler = ModulesHandler::new(&bus, "data".into(), ModulesHandlerOptions::default())?;
98//!
99//!     // Build all modules before starting — guarantees no message is lost.
100//!     handler.build_module::<Processor>(()).await?;
101//!
102//!     handler.start_modules().await?;
103//!     // Run until SIGINT / SIGTERM.
104//!     handler.exit_process().await
105//! }
106//! ```
107//!
108//! ## Architecture overview
109//!
110//! ```text
111//!  ┌────────────────────────────────────────────────┐
112//!  │               SharedMessageBus                 │
113//!  │   Arc<Mutex<AnyMap<broadcast::Sender<M>>>>     │
114//!  └─────────┬──────────────────────────┬───────────┘
115//!            │ subscribe                │ subscribe
116//!  ┌─────────▼──────────┐   ┌──────────▼──────────┐
117//!  │   Module A         │   │   Module B           │
118//!  │  (MempoolBusClient)│   │  (ConsensusBusClient)│
119//!  │   sender(EventA)   │──▶│   receiver(EventA)   │
120//!  └────────────────────┘   └─────────────────────-┘
121//! ```
122//!
123//! - Each message type gets one [`tokio::sync::broadcast`] channel, created on first use.
124//! - Cloning a bus handle gives access to the same underlying channels.
125//! - All communication is in-process: zero network hops, zero serialisation.
126//!
127//! ## Modules
128//!
129//! - [`bus`] — Core bus, traits, [`bus_client!`], [`handle_messages!`]
130//! - [`bus::command_response`] — Request/response pattern via [`Query`](bus::command_response::Query)
131//! - [`modules`] — [`Module`] trait, [`ModulesHandler`], shutdown signals
132//! - [`utils`] — Logging macros, profiling, static type maps, checksummed persistence
133
134pub mod bus;
135pub mod modules;
136pub mod utils;
137
138pub use opentelemetry::KeyValue;
139
140// Re-export commonly used types and functions
141pub use bus::{
142    BusClientReceiver, BusClientSender, BusEnvelope, BusMessage, BusReceiver, BusSender,
143    SharedMessageBus, DEFAULT_CAPACITY, LOW_CAPACITY,
144};
145pub use modules::{Module, ModulesHandler, ShutdownClient};
146
147// Macros are automatically exported at crate root via #[macro_export]
148// Available macros: bus_client, handle_messages, info_span_ctx,
149// log_debug, log_error, log_warn, module_bus_client, module_handle_messages