Skip to main content

reinhardt_core/signals/
distributed.rs

1#![cfg(native)]
2
3//! Distributed Signals - Cross-service signal dispatch via message brokers
4//!
5//! This module provides distributed signal support, allowing signals to be
6//! dispatched across multiple service instances via message brokers like
7//! Redis Pub/Sub, RabbitMQ, or Kafka.
8//!
9//! # Examples
10//!
11//! ```rust,no_run
12//! # #[tokio::main]
13//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
14//! use reinhardt_core::signals::distributed::{DistributedSignal, InMemoryBroker};
15//!
16//! // Create a distributed signal with an in-memory broker
17//! let broker = InMemoryBroker::new();
18//! let signal = DistributedSignal::<serde_json::Value, _>::new("user_created", broker, "service-1");
19//!
20//! // Subscribe to signals from other services
21//! signal.subscribe(|event| {
22//!     println!("Received distributed signal: {:?}", event);
23//!     Ok(())
24//! }).await?;
25//!
26//! // Publish signals to other services
27//! let user_event = serde_json::json!({"user_id": 123});
28//! signal.publish(user_event).await?;
29//! # Ok(())
30//! # }
31
32use super::error::SignalError;
33use async_trait::async_trait;
34use serde::{Deserialize, Serialize, de::DeserializeOwned};
35use std::fmt;
36use std::marker::PhantomData;
37use std::sync::Arc;
38use tokio::sync::RwLock;
39
40/// Distributed signal event wrapper
41///
42/// # Examples
43///
44/// ```
45/// use reinhardt_core::signals::distributed::DistributedEvent;
46/// use serde::{Deserialize, Serialize};
47///
48/// #[derive(Serialize, Deserialize, Clone)]
49/// struct UserEvent { user_id: i64 }
50///
51/// let event = DistributedEvent::new(
52///     "user_created",
53///     UserEvent { user_id: 123 },
54///     "service-1"
55/// );
56/// assert_eq!(event.signal_name, "user_created");
57/// assert_eq!(event.source_service, "service-1");
58/// ```
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct DistributedEvent<T> {
61	/// Signal name
62	pub signal_name: String,
63	/// Event payload
64	pub payload: T,
65	/// Source service identifier
66	pub source_service: String,
67	/// Event timestamp (Unix timestamp in milliseconds)
68	pub timestamp: u64,
69	/// Unique event ID
70	pub event_id: String,
71}
72
73impl<T> DistributedEvent<T> {
74	/// Create a new distributed event
75	///
76	/// # Examples
77	///
78	/// ```
79	/// use reinhardt_core::signals::distributed::DistributedEvent;
80	///
81	/// let event = DistributedEvent::new("test_signal", "payload", "service-1");
82	/// assert_eq!(event.signal_name, "test_signal");
83	/// ```
84	pub fn new(
85		signal_name: impl Into<String>,
86		payload: T,
87		source_service: impl Into<String>,
88	) -> Self {
89		use std::time::{SystemTime, UNIX_EPOCH};
90
91		let timestamp = SystemTime::now()
92			.duration_since(UNIX_EPOCH)
93			.unwrap_or_default()
94			.as_millis() as u64;
95
96		Self {
97			signal_name: signal_name.into(),
98			payload,
99			source_service: source_service.into(),
100			timestamp,
101			event_id: uuid::Uuid::now_v7().to_string(),
102		}
103	}
104}
105
106/// Message broker trait for distributed signals
107///
108/// Implement this trait to support different message brokers
109#[async_trait]
110pub trait MessageBroker: Send + Sync {
111	/// Publish a message to a channel
112	async fn publish(&self, channel: &str, message: &[u8]) -> Result<(), SignalError>;
113
114	/// Subscribe to a channel
115	async fn subscribe(
116		&self,
117		channel: &str,
118		handler: Arc<dyn Fn(Vec<u8>) -> Result<(), SignalError> + Send + Sync>,
119	) -> Result<(), SignalError>;
120
121	/// Unsubscribe from a channel
122	async fn unsubscribe(&self, channel: &str) -> Result<(), SignalError>;
123}
124
125/// In-memory message broker for testing and local development
126///
127/// Type alias for channel subscriber function
128type SubscriberFn = Arc<dyn Fn(Vec<u8>) -> Result<(), SignalError> + Send + Sync>;
129
130/// Type alias for channels map
131type ChannelsMap = std::collections::HashMap<String, Vec<SubscriberFn>>;
132
133/// # Examples
134///
135/// ```
136/// use reinhardt_core::signals::distributed::InMemoryBroker;
137///
138/// let broker = InMemoryBroker::new();
139/// ```
140pub struct InMemoryBroker {
141	channels: Arc<RwLock<ChannelsMap>>,
142}
143
144impl InMemoryBroker {
145	/// Create a new in-memory broker
146	///
147	/// # Examples
148	///
149	/// ```
150	/// use reinhardt_core::signals::distributed::InMemoryBroker;
151	///
152	/// let broker = InMemoryBroker::new();
153	/// ```
154	pub fn new() -> Self {
155		Self {
156			channels: Arc::new(RwLock::new(std::collections::HashMap::new())),
157		}
158	}
159}
160
161impl Default for InMemoryBroker {
162	fn default() -> Self {
163		Self::new()
164	}
165}
166
167#[async_trait]
168impl MessageBroker for InMemoryBroker {
169	async fn publish(&self, channel: &str, message: &[u8]) -> Result<(), SignalError> {
170		let channels = self.channels.read().await;
171		if let Some(handlers) = channels.get(channel) {
172			for handler in handlers {
173				handler(message.to_vec())?;
174			}
175		}
176		Ok(())
177	}
178
179	async fn subscribe(
180		&self,
181		channel: &str,
182		handler: Arc<dyn Fn(Vec<u8>) -> Result<(), SignalError> + Send + Sync>,
183	) -> Result<(), SignalError> {
184		let mut channels = self.channels.write().await;
185		channels
186			.entry(channel.to_string())
187			.or_insert_with(Vec::new)
188			.push(handler);
189		Ok(())
190	}
191
192	async fn unsubscribe(&self, channel: &str) -> Result<(), SignalError> {
193		let mut channels = self.channels.write().await;
194		channels.remove(channel);
195		Ok(())
196	}
197}
198
199/// Distributed signal with message broker integration
200///
201/// # Examples
202///
203/// ```
204/// use reinhardt_core::signals::distributed::{DistributedSignal, InMemoryBroker};
205///
206/// # async fn example() {
207/// let broker = InMemoryBroker::new();
208/// let signal = DistributedSignal::<String, _>::new("my_signal", broker, "service-1");
209/// # }
210/// ```
211pub struct DistributedSignal<T, B>
212where
213	T: Serialize + DeserializeOwned + Send + Sync + 'static,
214	B: MessageBroker + 'static,
215{
216	signal_name: String,
217	broker: Arc<B>,
218	service_id: String,
219	_phantom: PhantomData<T>,
220}
221
222impl<T, B> DistributedSignal<T, B>
223where
224	T: Serialize + DeserializeOwned + Send + Sync + 'static,
225	B: MessageBroker + 'static,
226{
227	/// Create a new distributed signal
228	///
229	/// # Examples
230	///
231	/// ```
232	/// use reinhardt_core::signals::distributed::{DistributedSignal, InMemoryBroker};
233	///
234	/// let broker = InMemoryBroker::new();
235	/// let signal = DistributedSignal::<String, _>::new("test", broker, "svc-1");
236	/// ```
237	pub fn new(signal_name: impl Into<String>, broker: B, service_id: impl Into<String>) -> Self {
238		Self {
239			signal_name: signal_name.into(),
240			broker: Arc::new(broker),
241			service_id: service_id.into(),
242			_phantom: PhantomData,
243		}
244	}
245
246	/// Get the signal name
247	///
248	/// # Examples
249	///
250	/// ```
251	/// use reinhardt_core::signals::distributed::{DistributedSignal, InMemoryBroker};
252	///
253	/// let broker = InMemoryBroker::new();
254	/// let signal = DistributedSignal::<String, _>::new("my_signal", broker, "svc-1");
255	/// assert_eq!(signal.name(), "my_signal");
256	/// ```
257	pub fn name(&self) -> &str {
258		&self.signal_name
259	}
260
261	/// Get the service ID
262	///
263	/// # Examples
264	///
265	/// ```
266	/// use reinhardt_core::signals::distributed::{DistributedSignal, InMemoryBroker};
267	///
268	/// let broker = InMemoryBroker::new();
269	/// let signal = DistributedSignal::<String, _>::new("test", broker, "service-1");
270	/// assert_eq!(signal.service_id(), "service-1");
271	/// ```
272	pub fn service_id(&self) -> &str {
273		&self.service_id
274	}
275
276	/// Publish an event to the distributed signal
277	///
278	/// # Examples
279	///
280	/// ```rust,no_run
281	/// use reinhardt_core::signals::distributed::{DistributedSignal, InMemoryBroker};
282	///
283	/// # #[tokio::main]
284	/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
285	/// let broker = InMemoryBroker::new();
286	/// let signal = DistributedSignal::new("user_created", broker, "api-service");
287	///
288	/// signal.publish("User data".to_string()).await?;
289	/// # Ok(())
290	/// # }
291	/// ```
292	pub async fn publish(&self, payload: T) -> Result<(), SignalError> {
293		let event = DistributedEvent::new(&self.signal_name, payload, &self.service_id);
294		let message = serde_json::to_vec(&event)
295			.map_err(|e| SignalError::new(format!("Serialization error: {}", e)))?;
296
297		self.broker.publish(&self.signal_name, &message).await
298	}
299
300	/// Subscribe to distributed signal events
301	///
302	/// # Examples
303	///
304	/// ```rust,no_run
305	/// use reinhardt_core::signals::distributed::{DistributedSignal, InMemoryBroker};
306	///
307	/// # #[tokio::main]
308	/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
309	/// let broker = InMemoryBroker::new();
310	/// let signal = DistributedSignal::<String, _>::new("user_created", broker, "worker-service");
311	///
312	/// signal.subscribe(|event| {
313	///     println!("Received: {:?}", event);
314	///     Ok(())
315	/// }).await?;
316	/// # Ok(())
317	/// # }
318	/// ```
319	pub async fn subscribe<F>(&self, handler: F) -> Result<(), SignalError>
320	where
321		F: Fn(DistributedEvent<T>) -> Result<(), SignalError> + Send + Sync + 'static,
322	{
323		let handler = Arc::new(handler);
324		let wrapped_handler = Arc::new(move |message: Vec<u8>| {
325			let event: DistributedEvent<T> = serde_json::from_slice(&message)
326				.map_err(|e| SignalError::new(format!("Deserialization error: {}", e)))?;
327			handler(event)
328		});
329
330		self.broker
331			.subscribe(&self.signal_name, wrapped_handler)
332			.await
333	}
334
335	/// Unsubscribe from distributed signal events
336	///
337	/// # Examples
338	///
339	/// ```rust,no_run
340	/// use reinhardt_core::signals::distributed::{DistributedSignal, InMemoryBroker};
341	///
342	/// # #[tokio::main]
343	/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
344	/// let broker = InMemoryBroker::new();
345	/// let signal = DistributedSignal::<String, _>::new("user_created", broker, "worker-service");
346	///
347	/// signal.unsubscribe().await?;
348	/// # Ok(())
349	/// # }
350	/// ```
351	pub async fn unsubscribe(&self) -> Result<(), SignalError> {
352		self.broker.unsubscribe(&self.signal_name).await
353	}
354}
355
356impl<T, B> Clone for DistributedSignal<T, B>
357where
358	T: Serialize + DeserializeOwned + Send + Sync + 'static,
359	B: MessageBroker + 'static,
360{
361	fn clone(&self) -> Self {
362		Self {
363			signal_name: self.signal_name.clone(),
364			broker: Arc::clone(&self.broker),
365			service_id: self.service_id.clone(),
366			_phantom: PhantomData,
367		}
368	}
369}
370
371impl<T, B> fmt::Debug for DistributedSignal<T, B>
372where
373	T: Serialize + DeserializeOwned + Send + Sync + 'static,
374	B: MessageBroker + 'static,
375{
376	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377		f.debug_struct("DistributedSignal")
378			.field("signal_name", &self.signal_name)
379			.field("service_id", &self.service_id)
380			.finish()
381	}
382}
383
384#[cfg(test)]
385mod tests {
386	use super::*;
387	use parking_lot::Mutex;
388	use std::sync::Arc;
389	use std::sync::atomic::{AtomicUsize, Ordering};
390
391	#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
392	struct TestPayload {
393		message: String,
394		count: i32,
395	}
396
397	#[test]
398	fn test_distributed_event_creation() {
399		let event = DistributedEvent::new("test_signal", "test_payload", "service-1");
400		assert_eq!(event.signal_name, "test_signal");
401		assert_eq!(event.payload, "test_payload");
402		assert_eq!(event.source_service, "service-1");
403		assert!(event.timestamp > 0);
404		assert!(!event.event_id.is_empty());
405	}
406
407	#[tokio::test]
408	async fn test_in_memory_broker() {
409		let broker = InMemoryBroker::new();
410		let received = Arc::new(Mutex::new(Vec::new()));
411
412		let r = received.clone();
413		let handler = Arc::new(move |msg: Vec<u8>| {
414			r.lock().push(msg);
415			Ok(())
416		});
417
418		broker.subscribe("test_channel", handler).await.unwrap();
419		broker.publish("test_channel", b"hello").await.unwrap();
420
421		let messages = received.lock();
422		assert_eq!(messages.len(), 1);
423		assert_eq!(messages[0], b"hello");
424	}
425
426	#[tokio::test]
427	async fn test_distributed_signal_publish_subscribe() {
428		let broker = InMemoryBroker::new();
429		let signal = DistributedSignal::new("user_event", broker, "service-1");
430
431		let received = Arc::new(Mutex::new(Vec::new()));
432		let r = received.clone();
433
434		signal
435			.subscribe(move |event| {
436				r.lock().push(event.payload);
437				Ok(())
438			})
439			.await
440			.unwrap();
441
442		let payload = TestPayload {
443			message: "Hello".to_string(),
444			count: 42,
445		};
446
447		signal.publish(payload.clone()).await.unwrap();
448
449		let events = received.lock();
450		assert_eq!(events.len(), 1);
451		assert_eq!(events[0], payload);
452	}
453
454	#[tokio::test]
455	async fn test_distributed_signal_multiple_subscribers() {
456		let broker = InMemoryBroker::new();
457		let signal = DistributedSignal::new("broadcast", broker, "svc-1");
458
459		let counter = Arc::new(AtomicUsize::new(0));
460
461		let c1 = counter.clone();
462		signal
463			.subscribe(move |_event: DistributedEvent<String>| {
464				c1.fetch_add(1, Ordering::SeqCst);
465				Ok(())
466			})
467			.await
468			.unwrap();
469
470		let c2 = counter.clone();
471		signal
472			.subscribe(move |_event| {
473				c2.fetch_add(1, Ordering::SeqCst);
474				Ok(())
475			})
476			.await
477			.unwrap();
478
479		signal.publish("test".to_string()).await.unwrap();
480
481		assert_eq!(counter.load(Ordering::SeqCst), 2);
482	}
483
484	#[tokio::test]
485	async fn test_distributed_signal_name_and_service_id() {
486		let broker = InMemoryBroker::new();
487		let signal = DistributedSignal::<String, _>::new("my_signal", broker, "my_service");
488
489		assert_eq!(signal.name(), "my_signal");
490		assert_eq!(signal.service_id(), "my_service");
491	}
492
493	#[tokio::test]
494	async fn test_distributed_signal_unsubscribe() {
495		let broker = InMemoryBroker::new();
496		let signal = DistributedSignal::new("temp_signal", broker, "svc-1");
497
498		let counter = Arc::new(AtomicUsize::new(0));
499		let c = counter.clone();
500
501		signal
502			.subscribe(move |_event: DistributedEvent<String>| {
503				c.fetch_add(1, Ordering::SeqCst);
504				Ok(())
505			})
506			.await
507			.unwrap();
508
509		signal.publish("first".to_string()).await.unwrap();
510		assert_eq!(counter.load(Ordering::SeqCst), 1);
511
512		signal.unsubscribe().await.unwrap();
513		signal.publish("second".to_string()).await.unwrap();
514
515		// Still 1 because we unsubscribed
516		assert_eq!(counter.load(Ordering::SeqCst), 1);
517	}
518
519	#[tokio::test]
520	async fn test_distributed_event_metadata() {
521		let broker = InMemoryBroker::new();
522		let signal = DistributedSignal::new("metadata_test", broker, "test-service");
523
524		let received_event = Arc::new(Mutex::new(None));
525		let r = received_event.clone();
526
527		signal
528			.subscribe(move |event: DistributedEvent<String>| {
529				*r.lock() = Some(event);
530				Ok(())
531			})
532			.await
533			.unwrap();
534
535		signal.publish("payload".to_string()).await.unwrap();
536
537		let event = received_event.lock().clone().unwrap();
538		assert_eq!(event.signal_name, "metadata_test");
539		assert_eq!(event.source_service, "test-service");
540		assert_eq!(event.payload, "payload");
541		assert!(event.timestamp > 0);
542		assert!(!event.event_id.is_empty());
543	}
544}