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