Skip to main content

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}