1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
use crate::{CamelError, MetricsCollector};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
/// Status of a Lifecycle service.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ServiceStatus {
Stopped,
Started,
Failed,
}
/// Aggregated system health status.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum HealthStatus {
Healthy,
Unhealthy,
}
/// Lifecycle trait for background services.
///
/// This trait follows Apache Camel's Service pattern but uses a different name
/// to avoid confusion with tower::Service which is the core of rust-camel's
/// request processing.
///
/// # Why `&mut self`?
///
/// The `start()` and `stop()` methods require `&mut self` to ensure:
/// - **Exclusive access**: Prevents concurrent start/stop operations on the same service
/// - **Safe state transitions**: Services can safely mutate their internal state
/// - **No data races**: Compile-time guarantee of single-threaded access to service state
///
/// This design choice trades flexibility for safety - services cannot be started/stopped
/// concurrently, which simplifies implementation and prevents race conditions.
#[async_trait]
pub trait Lifecycle: Send + Sync {
/// Service name for logging
fn name(&self) -> &str;
/// Start service (called during CamelContext.start())
async fn start(&mut self) -> Result<(), CamelError>;
/// Stop service (called during CamelContext.stop())
async fn stop(&mut self) -> Result<(), CamelError>;
/// Optional: expose MetricsCollector for auto-registration
fn as_metrics_collector(&self) -> Option<Arc<dyn MetricsCollector>> {
None
}
/// Current status of the service.
fn status(&self) -> ServiceStatus {
ServiceStatus::Stopped
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestService;
#[async_trait]
impl Lifecycle for TestService {
fn name(&self) -> &str {
"test"
}
async fn start(&mut self) -> Result<(), CamelError> {
Ok(())
}
async fn stop(&mut self) -> Result<(), CamelError> {
Ok(())
}
}
#[tokio::test]
async fn test_lifecycle_trait() {
let mut service = TestService;
assert_eq!(service.name(), "test");
service.start().await.unwrap();
service.stop().await.unwrap();
}
#[test]
fn test_default_status_is_stopped() {
let service = TestService;
assert_eq!(service.status(), ServiceStatus::Stopped);
}
}