mecha10_core/service_manager.rs
1//! Service Manager for Lifecycle Management
2//!
3//! This module provides centralized management of Service lifecycles, including
4//! registration, startup, shutdown, and health monitoring.
5//!
6//! # Architecture
7//!
8//! ```text
9//! ┌────────────────────────────────────────────────────────────┐
10//! │ ServiceManager │
11//! ├────────────────────────────────────────────────────────────┤
12//! │ │
13//! │ 1. Register services (init phase) │
14//! │ ├─ HTTP API Service │
15//! │ ├─ Database Service │
16//! │ └─ Job Processor Service │
17//! │ │
18//! │ 2. Start all services (in registration order) │
19//! │ └─ Spawn background tasks │
20//! │ │
21//! │ 3. Monitor health │
22//! │ └─ Periodic health checks │
23//! │ │
24//! │ 4. Stop all services (reverse order) │
25//! │ └─ Graceful shutdown │
26//! │ │
27//! └────────────────────────────────────────────────────────────┘
28//! ```
29//!
30//! # Example
31//!
32//! ```rust
33//! use mecha10::prelude::*;
34//! use mecha10::service::{Service, ServiceManager};
35//!
36//! # async fn example() -> Result<()> {
37//! // Create service manager
38//! let mut service_manager = ServiceManager::new();
39//!
40//! // Register services
41//! service_manager.register::<HttpApiService>(http_config).await?;
42//! service_manager.register::<DatabaseService>(db_config).await?;
43//!
44//! // Start all services
45//! service_manager.start_all().await?;
46//!
47//! // ... application runs ...
48//!
49//! // Wait for shutdown signal
50//! tokio::signal::ctrl_c().await?;
51//!
52//! // Stop all services (in reverse order)
53//! service_manager.stop_all().await?;
54//! # Ok(())
55//! # }
56//! # #[derive(Debug)] struct HttpApiService;
57//! # #[derive(Debug)] struct DatabaseService;
58//! # #[derive(Debug, serde::Deserialize)] struct HttpConfig;
59//! # #[derive(Debug, serde::Deserialize)] struct DbConfig;
60//! # #[async_trait::async_trait]
61//! # impl Service for HttpApiService {
62//! # type Config = HttpConfig;
63//! # async fn init(_: Self::Config) -> Result<Self> { Ok(Self) }
64//! # async fn start(&mut self) -> Result<()> { Ok(()) }
65//! # async fn stop(&mut self) -> Result<()> { Ok(()) }
66//! # fn name(&self) -> &str { "http" }
67//! # }
68//! # #[async_trait::async_trait]
69//! # impl Service for DatabaseService {
70//! # type Config = DbConfig;
71//! # async fn init(_: Self::Config) -> Result<Self> { Ok(Self) }
72//! # async fn start(&mut self) -> Result<()> { Ok(()) }
73//! # async fn stop(&mut self) -> Result<()> { Ok(()) }
74//! # fn name(&self) -> &str { "db" }
75//! # }
76//! # let http_config = HttpConfig;
77//! # let db_config = DbConfig;
78//! ```
79
80use crate::service::Service;
81use crate::{HealthStatus, Result};
82use async_trait::async_trait;
83use tokio::sync::broadcast;
84use tracing::{debug, error, info};
85
86// ============================================================================
87// Type-Erased Service Wrapper
88// ============================================================================
89
90/// Internal trait for type-erased service management.
91///
92/// This trait wraps the `Service` trait and erases the associated `Config` type,
93/// allowing services with different config types to be stored in a single collection.
94#[async_trait]
95trait ServiceWrapper: Send + Sync {
96 async fn start(&mut self) -> Result<()>;
97 async fn stop(&mut self) -> Result<()>;
98 async fn health_check(&self) -> HealthStatus;
99 fn name(&self) -> &str;
100}
101
102/// Wrapper that implements ServiceWrapper for any Service type.
103struct ServiceBox<S: Service> {
104 service: S,
105}
106
107#[async_trait]
108impl<S: Service> ServiceWrapper for ServiceBox<S> {
109 async fn start(&mut self) -> Result<()> {
110 self.service.start().await
111 }
112
113 async fn stop(&mut self) -> Result<()> {
114 self.service.stop().await
115 }
116
117 async fn health_check(&self) -> HealthStatus {
118 self.service.health_check().await
119 }
120
121 fn name(&self) -> &str {
122 self.service.name()
123 }
124}
125
126// ============================================================================
127// Service Manager
128// ============================================================================
129
130/// Centralized manager for service lifecycles.
131///
132/// The ServiceManager is responsible for:
133/// - Registering services with configuration
134/// - Starting services in order
135/// - Monitoring service health
136/// - Stopping services gracefully (in reverse order)
137/// - Broadcasting shutdown signals
138///
139/// # Lifecycle
140///
141/// 1. **Register Phase** - `register()` services with config (calls `Service::init()`)
142/// 2. **Start Phase** - `start_all()` starts all services (calls `Service::start()`)
143/// 3. **Running Phase** - Services execute, manager monitors health
144/// 4. **Stop Phase** - `stop_all()` stops services in reverse order (calls `Service::stop()`)
145///
146/// # Example
147///
148/// ```rust
149/// # use mecha10::prelude::*;
150/// # use mecha10::service::{Service, ServiceManager};
151/// # async fn example() -> Result<()> {
152/// let mut manager = ServiceManager::new();
153///
154/// // Register services (init phase)
155/// manager.register::<MyService>(config).await?;
156///
157/// // Start all services
158/// manager.start_all().await?;
159///
160/// // Check health
161/// let health = manager.health_check_all().await;
162/// for (name, status) in health {
163/// println!("{}: {:?}", name, status);
164/// }
165///
166/// // Graceful shutdown
167/// manager.stop_all().await?;
168/// # Ok(())
169/// # }
170/// # #[derive(Debug)] struct MyService;
171/// # #[derive(Debug, serde::Deserialize)] struct MyConfig;
172/// # #[async_trait::async_trait]
173/// # impl Service for MyService {
174/// # type Config = MyConfig;
175/// # async fn init(_: Self::Config) -> Result<Self> { Ok(Self) }
176/// # async fn start(&mut self) -> Result<()> { Ok(()) }
177/// # async fn stop(&mut self) -> Result<()> { Ok(()) }
178/// # fn name(&self) -> &str { "my_service" }
179/// # }
180/// # let config = MyConfig;
181/// ```
182pub struct ServiceManager {
183 /// Registered services (in registration order)
184 services: Vec<Box<dyn ServiceWrapper>>,
185
186 /// Shutdown broadcast channel
187 shutdown_tx: broadcast::Sender<()>,
188}
189
190impl ServiceManager {
191 /// Create a new ServiceManager.
192 ///
193 /// # Example
194 ///
195 /// ```rust
196 /// # use mecha10::service::ServiceManager;
197 /// let manager = ServiceManager::new();
198 /// ```
199 pub fn new() -> Self {
200 let (shutdown_tx, _) = broadcast::channel(1);
201 Self {
202 services: Vec::new(),
203 shutdown_tx,
204 }
205 }
206
207 /// Register a service with configuration.
208 ///
209 /// Calls `Service::init()` to initialize the service, then stores it
210 /// for later startup. Services are started in the order they are registered.
211 ///
212 /// # Arguments
213 ///
214 /// * `config` - Service-specific configuration
215 ///
216 /// # Returns
217 ///
218 /// * `Ok(())` - Service registered successfully
219 /// * `Err(_)` - Service initialization failed
220 ///
221 /// # Example
222 ///
223 /// ```rust
224 /// # use mecha10::prelude::*;
225 /// # use mecha10::service::{Service, ServiceManager};
226 /// # async fn example() -> Result<()> {
227 /// let mut manager = ServiceManager::new();
228 ///
229 /// // Register HTTP API
230 /// manager.register::<HttpApiService>(HttpApiConfig {
231 /// host: "0.0.0.0".to_string(),
232 /// port: 8080,
233 /// }).await?;
234 ///
235 /// // Register database
236 /// manager.register::<DatabaseService>(DatabaseConfig {
237 /// url: "postgresql://localhost/mecha10".to_string(),
238 /// max_connections: 20,
239 /// }).await?;
240 /// # Ok(())
241 /// # }
242 /// # #[derive(Debug)] struct HttpApiService;
243 /// # #[derive(Debug)] struct DatabaseService;
244 /// # #[derive(Debug, serde::Deserialize)] struct HttpApiConfig { host: String, port: u16 }
245 /// # #[derive(Debug, serde::Deserialize)] struct DatabaseConfig { url: String, max_connections: u32 }
246 /// # #[async_trait::async_trait]
247 /// # impl Service for HttpApiService {
248 /// # type Config = HttpApiConfig;
249 /// # async fn init(_: Self::Config) -> Result<Self> { Ok(Self) }
250 /// # async fn start(&mut self) -> Result<()> { Ok(()) }
251 /// # async fn stop(&mut self) -> Result<()> { Ok(()) }
252 /// # fn name(&self) -> &str { "http_api" }
253 /// # }
254 /// # #[async_trait::async_trait]
255 /// # impl Service for DatabaseService {
256 /// # type Config = DatabaseConfig;
257 /// # async fn init(_: Self::Config) -> Result<Self> { Ok(Self) }
258 /// # async fn start(&mut self) -> Result<()> { Ok(()) }
259 /// # async fn stop(&mut self) -> Result<()> { Ok(()) }
260 /// # fn name(&self) -> &str { "database" }
261 /// # }
262 /// ```
263 pub async fn register<S>(&mut self, config: S::Config) -> Result<()>
264 where
265 S: Service + 'static,
266 {
267 debug!("Registering service: {}", std::any::type_name::<S>());
268
269 let service = S::init(config).await?;
270 let name = service.name().to_string();
271
272 // Wrap the service in ServiceBox for type erasure
273 let boxed = Box::new(ServiceBox { service });
274
275 // Store the type-erased service
276 self.services.push(boxed);
277
278 info!("Registered service: {}", name);
279 Ok(())
280 }
281
282 /// Start all registered services.
283 ///
284 /// Services are started in the order they were registered. If any service
285 /// fails to start, the error is returned and remaining services are not started.
286 ///
287 /// # Returns
288 ///
289 /// * `Ok(())` - All services started successfully
290 /// * `Err(_)` - A service failed to start
291 ///
292 /// # Example
293 ///
294 /// ```rust
295 /// # use mecha10::prelude::*;
296 /// # use mecha10::service::ServiceManager;
297 /// # async fn example() -> Result<()> {
298 /// let mut manager = ServiceManager::new();
299 /// // ... register services ...
300 ///
301 /// // Start all services
302 /// manager.start_all().await?;
303 ///
304 /// info!("All services started successfully");
305 /// # Ok(())
306 /// # }
307 /// ```
308 pub async fn start_all(&mut self) -> Result<()> {
309 info!("Starting {} services", self.services.len());
310
311 for service in &mut self.services {
312 let name = service.name().to_string();
313 debug!("Starting service: {}", name);
314
315 if let Err(e) = service.start().await {
316 error!("Failed to start service '{}': {}", name, e);
317 return Err(e);
318 }
319
320 info!("Started service: {}", name);
321 }
322
323 info!("All services started successfully");
324 Ok(())
325 }
326
327 /// Stop all services gracefully.
328 ///
329 /// Services are stopped in **reverse order** of registration. This ensures
330 /// that dependent services are stopped first (e.g., stop API server before database).
331 ///
332 /// Each service's `stop()` method is called, even if previous services failed
333 /// to stop. All errors are logged, and the first error encountered is returned.
334 ///
335 /// # Returns
336 ///
337 /// * `Ok(())` - All services stopped successfully
338 /// * `Err(_)` - At least one service failed to stop
339 ///
340 /// # Example
341 ///
342 /// ```rust
343 /// # use mecha10::prelude::*;
344 /// # use mecha10::service::ServiceManager;
345 /// # async fn example() -> Result<()> {
346 /// # let mut manager = ServiceManager::new();
347 /// // ... services running ...
348 ///
349 /// // Graceful shutdown
350 /// tokio::signal::ctrl_c().await?;
351 /// manager.stop_all().await?;
352 /// # Ok(())
353 /// # }
354 /// ```
355 pub async fn stop_all(&mut self) -> Result<()> {
356 info!("Stopping {} services (in reverse order)", self.services.len());
357
358 let mut first_error = None;
359
360 // Stop in reverse order
361 for service in self.services.iter_mut().rev() {
362 let name = service.name().to_string();
363 debug!("Stopping service: {}", name);
364
365 match service.stop().await {
366 Ok(()) => {
367 info!("Stopped service: {}", name);
368 }
369 Err(e) => {
370 error!("Failed to stop service '{}': {}", name, e);
371 if first_error.is_none() {
372 first_error = Some(e);
373 }
374 }
375 }
376 }
377
378 if let Some(err) = first_error {
379 error!("Service manager stopped with errors");
380 Err(err)
381 } else {
382 info!("All services stopped successfully");
383 Ok(())
384 }
385 }
386
387 /// Check health of all services.
388 ///
389 /// Calls `health_check()` on each registered service and returns a vector
390 /// of (name, status) tuples.
391 ///
392 /// # Returns
393 ///
394 /// * `Vec<(String, HealthStatus)>` - Health status for each service
395 ///
396 /// # Example
397 ///
398 /// ```rust
399 /// # use mecha10::prelude::*;
400 /// # use mecha10::service::ServiceManager;
401 /// # async fn example() -> Result<()> {
402 /// # let manager = ServiceManager::new();
403 /// let health = manager.health_check_all().await;
404 ///
405 /// for (name, status) in health {
406 /// if !status.healthy {
407 /// warn!("Service '{}' is unhealthy: {:?}", name, status.message);
408 /// } else {
409 /// info!("Service '{}' is healthy", name);
410 /// }
411 /// }
412 /// # Ok(())
413 /// # }
414 /// ```
415 pub async fn health_check_all(&self) -> Vec<(String, HealthStatus)> {
416 let mut results = Vec::new();
417
418 for service in &self.services {
419 let name = service.name().to_string();
420 let status = service.health_check().await;
421
422 debug!("Health check for '{}': healthy={}", name, status.healthy);
423 results.push((name, status));
424 }
425
426 results
427 }
428
429 /// Get a broadcast receiver for shutdown signals.
430 ///
431 /// Services can use this to listen for shutdown signals and stop gracefully.
432 ///
433 /// # Returns
434 ///
435 /// * `broadcast::Receiver<()>` - Shutdown signal receiver
436 ///
437 /// # Example
438 ///
439 /// ```rust
440 /// # use mecha10::service::ServiceManager;
441 /// # async fn example() {
442 /// let manager = ServiceManager::new();
443 /// let mut shutdown_rx = manager.shutdown_signal();
444 ///
445 /// tokio::spawn(async move {
446 /// shutdown_rx.recv().await.ok();
447 /// // Service cleanup...
448 /// });
449 /// # }
450 /// ```
451 pub fn shutdown_signal(&self) -> broadcast::Receiver<()> {
452 self.shutdown_tx.subscribe()
453 }
454
455 /// Trigger shutdown by broadcasting to all listeners.
456 ///
457 /// This sends a signal to all `shutdown_signal()` receivers. Services
458 /// should listen to this signal to stop gracefully.
459 ///
460 /// Note: This does NOT call `stop_all()`. You must call `stop_all()`
461 /// separately to actually stop services.
462 ///
463 /// # Example
464 ///
465 /// ```rust
466 /// # use mecha10::service::ServiceManager;
467 /// # async fn example() {
468 /// let mut manager = ServiceManager::new();
469 ///
470 /// // Trigger shutdown signal
471 /// manager.shutdown();
472 ///
473 /// // Then stop all services
474 /// manager.stop_all().await.ok();
475 /// # }
476 /// ```
477 pub fn shutdown(&self) {
478 info!("Broadcasting shutdown signal");
479 let _ = self.shutdown_tx.send(());
480 }
481
482 /// Get the number of registered services.
483 ///
484 /// # Returns
485 ///
486 /// * `usize` - Number of services
487 ///
488 /// # Example
489 ///
490 /// ```rust
491 /// # use mecha10::service::ServiceManager;
492 /// # let manager = ServiceManager::new();
493 /// println!("Services registered: {}", manager.service_count());
494 /// ```
495 pub fn service_count(&self) -> usize {
496 self.services.len()
497 }
498
499 /// Get names of all registered services.
500 ///
501 /// # Returns
502 ///
503 /// * `Vec<String>` - Service names
504 ///
505 /// # Example
506 ///
507 /// ```rust
508 /// # use mecha10::service::ServiceManager;
509 /// # let manager = ServiceManager::new();
510 /// let names = manager.service_names();
511 /// println!("Services: {}", names.join(", "));
512 /// ```
513 pub fn service_names(&self) -> Vec<String> {
514 self.services.iter().map(|s| s.name().to_string()).collect()
515 }
516}
517
518impl Default for ServiceManager {
519 fn default() -> Self {
520 Self::new()
521 }
522}
523
524// ============================================================================
525// Tests
526// ============================================================================