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, 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}