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