rskit_component/component.rs
1use std::sync::Arc;
2
3use parking_lot::Mutex;
4use rskit_errors::AppResult;
5
6use crate::Health;
7
8/// Lifecycle-managed infrastructure component.
9///
10/// Implement this trait for databases, caches, servers, message brokers, and
11/// other infrastructure that must participate in ordered startup, shutdown, and
12/// health reporting.
13///
14/// # Example
15///
16/// ```rust
17/// use rskit_component::{Component, Health, Registry};
18/// use rskit_errors::AppResult;
19/// use std::sync::Arc;
20///
21/// struct Cache;
22///
23/// #[async_trait::async_trait]
24/// impl Component for Cache {
25/// fn name(&self) -> &str {
26/// "cache"
27/// }
28///
29/// async fn start(&self) -> AppResult<()> {
30/// Ok(())
31/// }
32///
33/// async fn stop(&self) -> AppResult<()> {
34/// Ok(())
35/// }
36///
37/// fn health(&self) -> Health {
38/// Health::healthy(self.name())
39/// }
40/// }
41///
42/// # #[tokio::main]
43/// # async fn main() -> AppResult<()> {
44/// let mut registry = Registry::new();
45/// registry.register(Arc::new(Cache));
46/// registry.start_all().await?;
47/// registry.stop_all().await?;
48/// # Ok(())
49/// # }
50/// ```
51#[async_trait::async_trait]
52pub trait Component: Send + Sync {
53 /// Stable identifier used in logs and health responses.
54 fn name(&self) -> &str;
55
56 /// Start the component.
57 ///
58 /// Implementations must be cancel-safe: if the future is dropped because a
59 /// registry timeout expires, a subsequent [`Component::stop`] call must be
60 /// able to clean up any partially initialized resources.
61 async fn start(&self) -> AppResult<()>;
62
63 /// Stop the component gracefully.
64 ///
65 /// Implementations must be cancel-safe and idempotent.
66 async fn stop(&self) -> AppResult<()>;
67
68 /// Return an instantaneous health snapshot.
69 fn health(&self) -> Health;
70}
71
72/// Wraps a component factory so construction is deferred until `start()`.
73pub struct LazyComponent<F> {
74 name: &'static str,
75 factory: F,
76 inner: Mutex<Option<Arc<dyn Component>>>,
77}
78
79impl<F: Fn() -> Arc<dyn Component> + Send + Sync> LazyComponent<F> {
80 /// Create a new lazy component with the given `name` and `factory`.
81 #[must_use]
82 pub fn new(name: &'static str, factory: F) -> Self {
83 Self {
84 name,
85 factory,
86 inner: Mutex::new(None),
87 }
88 }
89}
90
91#[async_trait::async_trait]
92impl<F: Fn() -> Arc<dyn Component> + Send + Sync> Component for LazyComponent<F> {
93 fn name(&self) -> &str {
94 self.name
95 }
96
97 async fn start(&self) -> AppResult<()> {
98 let component = {
99 let mut guard = self.inner.lock();
100 if let Some(component) = guard.as_ref() {
101 Arc::clone(component)
102 } else {
103 let component = (self.factory)();
104 *guard = Some(Arc::clone(&component));
105 component
106 }
107 };
108 component.start().await
109 }
110
111 async fn stop(&self) -> AppResult<()> {
112 let component = self.inner.lock().clone();
113 if let Some(component) = component {
114 component.stop().await
115 } else {
116 Ok(())
117 }
118 }
119
120 fn health(&self) -> Health {
121 let component = self.inner.lock().clone();
122 if let Some(component) = component {
123 component.health()
124 } else {
125 Health::healthy(self.name)
126 }
127 }
128}