mecha10-core 0.6.5

Mecha10 core runtime and foundational types
Documentation
//! Service Pattern for Infrastructure Components
//!
//! This module defines the Service trait for infrastructure and singleton components
//! that complement the Node pattern. While Nodes are designed for data processing
//! via pub/sub messaging, Services provide capabilities like HTTP servers, database
//! connections, job processors, and other infrastructure components.
//!
//! # Node vs Service
//!
//! | Aspect | Node | Service |
//! |--------|------|---------|
//! | **Purpose** | Data processing via pub/sub | Infrastructure/capabilities |
//! | **Lifecycle** | `init → run (loop) → shutdown` | `init → start → stop` |
//! | **Concurrency** | Single run loop | Multiple background tasks |
//! | **Instances** | Can have multiple (e.g., camera/left, camera/right) | Typically singleton |
//! | **Context** | Has Context for pub/sub | May use Context internally |
//! | **Examples** | camera_fake, lidar_filter, task_planner | HTTP API, database pool, job queue |
//!
//! # Example
//!
//! ```rust
//! use mecha10_core::prelude::*;
//! use mecha10_core::service::{Service, ServiceManager};
//! use axum::{Router, routing::get};
//!
//! #[derive(Debug)]
//! pub struct HttpApiService {
//!     config: HttpApiConfig,
//!     server_handle: Option<tokio::task::JoinHandle<()>>,
//!     shutdown_tx: Option<tokio::sync::oneshot::Sender<()>>,
//! }
//!
//! #[derive(Debug, serde::Deserialize)]
//! pub struct HttpApiConfig {
//!     pub host: String,
//!     pub port: u16,
//! }
//!
//! #[async_trait::async_trait]
//! impl Service for HttpApiService {
//!     type Config = HttpApiConfig;
//!
//!     async fn init(config: Self::Config) -> Result<Self> {
//!         Ok(Self {
//!             config,
//!             server_handle: None,
//!             shutdown_tx: None,
//!         })
//!     }
//!
//!     async fn start(&mut self) -> Result<()> {
//!         let app = Router::new().route("/health", get(|| async { "OK" }));
//!         let addr = format!("{}:{}", self.config.host, self.config.port);
//!         let listener = tokio::net::TcpListener::bind(&addr).await?;
//!
//!         let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
//!         self.shutdown_tx = Some(shutdown_tx);
//!
//!         let server_handle = tokio::spawn(async move {
//!             axum::serve(listener, app)
//!                 .with_graceful_shutdown(async {
//!                     shutdown_rx.await.ok();
//!                 })
//!                 .await
//!                 .expect("Server error");
//!         });
//!
//!         self.server_handle = Some(server_handle);
//!         info!("HTTP API started on {}", addr);
//!         Ok(())
//!     }
//!
//!     async fn stop(&mut self) -> Result<()> {
//!         if let Some(tx) = self.shutdown_tx.take() {
//!             let _ = tx.send(());
//!         }
//!
//!         if let Some(handle) = self.server_handle.take() {
//!             handle.await?;
//!         }
//!
//!         info!("HTTP API stopped");
//!         Ok(())
//!     }
//!
//!     fn name(&self) -> &str {
//!         "http_api"
//!     }
//! }
//! ```

use crate::{HealthStatus, Result};
use async_trait::async_trait;
use serde::de::DeserializeOwned;
use std::fmt::Debug;

/// Core trait for infrastructure and singleton components.
///
/// Services differ from Nodes in several ways:
/// - Services provide capabilities/infrastructure (HTTP servers, databases, job queues)
/// - Nodes process data via pub/sub (sensors, actuators, algorithms)
/// - Services are typically singletons
/// - Nodes can have multiple instances
/// - Services have start/stop lifecycle
/// - Nodes have continuous run loop
///
/// # Lifecycle
///
/// Services follow a three-phase lifecycle:
///
/// 1. **`init(config)`** - Initialize the service with configuration
///    - Called once at startup before start()
///    - Load configuration, validate settings
///    - Allocate resources (but don't start background tasks yet)
///
/// 2. **`start()`** - Start the service
///    - Spawn background tasks
///    - Start listening on ports/sockets
///    - Begin processing
///    - Should return quickly (tasks run in background)
///    - Called after all services are initialized
///
/// 3. **`stop()`** - Stop the service gracefully
///    - Signal background tasks to stop
///    - Wait for tasks to complete
///    - Release resources
///    - Called during shutdown
///
/// # Health Monitoring
///
/// Services should implement `health_check()` to report their status:
///
/// ```rust
/// # use mecha10_core::prelude::*;
/// # use mecha10_core::service::Service;
/// # struct MyService;
/// # #[derive(Debug, serde::Deserialize)] struct MyConfig;
/// # #[async_trait::async_trait]
/// # impl Service for MyService {
/// #     type Config = MyConfig;
/// #     async fn init(_config: Self::Config) -> Result<Self> { Ok(MyService) }
/// #     async fn start(&mut self) -> Result<()> { Ok(()) }
/// #     async fn stop(&mut self) -> Result<()> { Ok(()) }
/// #     fn name(&self) -> &str { "my_service" }
/// async fn health_check(&self) -> HealthStatus {
///     // Check if background tasks are running
///     if self.is_healthy() {
///         HealthStatus {
///             healthy: true,
///             last_check: mecha10_core::prelude::now_micros(),
///             failure_count: 0,
///             message: Some("All systems operational".to_string()),
///         }
///     } else {
///         HealthStatus {
///             healthy: false,
///             last_check: mecha10_core::prelude::now_micros(),
///             failure_count: 1,
///             message: Some("Service degraded".to_string()),
///         }
///     }
/// }
/// #     fn is_healthy(&self) -> bool { true }
/// # }
/// ```
#[async_trait]
pub trait Service: Send + Sync + Debug + 'static {
    /// Configuration type for this service
    type Config: DeserializeOwned + Send + Sync + Debug;

    /// Initialize the service with configuration.
    ///
    /// Called once at startup before `start()`. Use this to:
    /// - Load and validate configuration
    /// - Allocate resources
    /// - Prepare for startup (but don't start background tasks yet)
    ///
    /// # Arguments
    ///
    /// * `config` - Service configuration
    ///
    /// # Returns
    ///
    /// * `Ok(Self)` - Successfully initialized service
    /// * `Err(_)` - Initialization failed
    ///
    /// # Example
    ///
    /// ```rust
    /// # use mecha10_core::prelude::*;
    /// # use mecha10_core::service::Service;
    /// # #[derive(Debug)]
    /// # struct DatabaseService { pool: Option<()> }
    /// # #[derive(Debug, serde::Deserialize)]
    /// # struct DatabaseConfig { url: String, max_connections: u32 }
    /// # #[async_trait::async_trait]
    /// # impl Service for DatabaseService {
    /// # type Config = DatabaseConfig;
    /// async fn init(config: Self::Config) -> Result<Self> {
    ///     // Validate config
    ///     if config.max_connections == 0 {
    ///         return Err(anyhow::anyhow!("max_connections must be > 0").into());
    ///     }
    ///
    ///     Ok(Self {
    ///         pool: None,  // Will connect in start()
    ///     })
    /// }
    /// # async fn start(&mut self) -> Result<()> { Ok(()) }
    /// # async fn stop(&mut self) -> Result<()> { Ok(()) }
    /// # fn name(&self) -> &str { "database" }
    /// # }
    /// ```
    async fn init(config: Self::Config) -> Result<Self>
    where
        Self: Sized;

    /// Start the service.
    ///
    /// Called after all services are initialized. Use this to:
    /// - Spawn background tasks
    /// - Start listening on ports/sockets
    /// - Begin processing
    ///
    /// This method should return quickly - background work should be spawned
    /// as separate tasks.
    ///
    /// # Returns
    ///
    /// * `Ok(())` - Service started successfully
    /// * `Err(_)` - Startup failed
    ///
    /// # Example
    ///
    /// ```rust
    /// # use mecha10_core::prelude::*;
    /// # use mecha10_core::service::Service;
    /// # #[derive(Debug)]
    /// # struct JobProcessor {
    /// #     worker_handles: Vec<tokio::task::JoinHandle<()>>,
    /// #     job_tx: tokio::sync::mpsc::Sender<()>,
    /// # }
    /// # #[derive(Debug, serde::Deserialize)]
    /// # struct JobConfig { worker_count: usize }
    /// # #[async_trait::async_trait]
    /// # impl Service for JobProcessor {
    /// # type Config = JobConfig;
    /// # async fn init(_config: Self::Config) -> Result<Self> {
    /// #     let (job_tx, _) = tokio::sync::mpsc::channel(100);
    /// #     Ok(Self { worker_handles: Vec::new(), job_tx })
    /// # }
    /// async fn start(&mut self) -> Result<()> {
    ///     // Spawn worker tasks
    ///     for i in 0..4 {
    ///         let handle = tokio::spawn(async move {
    ///             info!("Worker {} started", i);
    ///             // Worker loop...
    ///         });
    ///         self.worker_handles.push(handle);
    ///     }
    ///
    ///     info!("Job processor started with {} workers", 4);
    ///     Ok(())
    /// }
    /// # async fn stop(&mut self) -> Result<()> { Ok(()) }
    /// # fn name(&self) -> &str { "job_processor" }
    /// # }
    /// ```
    async fn start(&mut self) -> Result<()>;

    /// Stop the service gracefully.
    ///
    /// Called during shutdown. Use this to:
    /// - Signal background tasks to stop
    /// - Wait for tasks to complete
    /// - Release resources (close connections, files, etc.)
    ///
    /// This method should block until all background work is complete.
    ///
    /// # Returns
    ///
    /// * `Ok(())` - Service stopped successfully
    /// * `Err(_)` - Shutdown failed (may leave resources in inconsistent state)
    ///
    /// # Example
    ///
    /// ```rust
    /// # use mecha10_core::prelude::*;
    /// # use mecha10_core::service::Service;
    /// # #[derive(Debug)]
    /// # struct DatabaseService { pool: Option<()> }
    /// # #[derive(Debug, serde::Deserialize)]
    /// # struct DatabaseConfig;
    /// # #[async_trait::async_trait]
    /// # impl Service for DatabaseService {
    /// # type Config = DatabaseConfig;
    /// # async fn init(_config: Self::Config) -> Result<Self> { Ok(Self { pool: None }) }
    /// # async fn start(&mut self) -> Result<()> { Ok(()) }
    /// async fn stop(&mut self) -> Result<()> {
    ///     if let Some(pool) = self.pool.take() {
    ///         // Close all connections
    ///         // pool.close().await;
    ///     }
    ///     info!("Database connection pool closed");
    ///     Ok(())
    /// }
    /// # fn name(&self) -> &str { "database" }
    /// # }
    /// ```
    async fn stop(&mut self) -> Result<()>;

    /// Check service health.
    ///
    /// Called periodically by health monitoring. Override this to provide
    /// meaningful health status.
    ///
    /// Default implementation returns healthy.
    ///
    /// # Returns
    ///
    /// * `HealthStatus` - Current health status
    ///
    /// # Example
    ///
    /// ```rust
    /// # use mecha10_core::prelude::*;
    /// # use mecha10_core::service::Service;
    /// # #[derive(Debug)]
    /// # struct HttpApiService { connected_clients: u32 }
    /// # #[derive(Debug, serde::Deserialize)]
    /// # struct HttpApiConfig;
    /// # #[async_trait::async_trait]
    /// # impl Service for HttpApiService {
    /// # type Config = HttpApiConfig;
    /// # async fn init(_config: Self::Config) -> Result<Self> {
    /// #     Ok(Self { connected_clients: 0 })
    /// # }
    /// # async fn start(&mut self) -> Result<()> { Ok(()) }
    /// # async fn stop(&mut self) -> Result<()> { Ok(()) }
    /// # fn name(&self) -> &str { "http_api" }
    /// async fn health_check(&self) -> HealthStatus {
    ///     let healthy = self.connected_clients < 1000;  // Check capacity
    ///
    ///     HealthStatus {
    ///         healthy,
    ///         last_check: now_micros(),
    ///         failure_count: if healthy { 0 } else { 1 },
    ///         message: Some(format!(
    ///             "Connected clients: {}/1000",
    ///             self.connected_clients
    ///         )),
    ///     }
    /// }
    /// # }
    /// ```
    async fn health_check(&self) -> HealthStatus {
        HealthStatus {
            healthy: true,
            last_check: crate::prelude::now_micros(),
            failure_count: 0,
            message: Some("Service operational".to_string()),
        }
    }

    /// Service name for logging and registration.
    ///
    /// This should be a unique identifier for the service type (not instance).
    ///
    /// # Returns
    ///
    /// * `&str` - Service name
    ///
    /// # Example
    ///
    /// ```rust
    /// # use mecha10_core::service::Service;
    /// # #[derive(Debug)]
    /// # struct HttpApiService;
    /// # #[derive(Debug, serde::Deserialize)]
    /// # struct HttpApiConfig;
    /// # #[async_trait::async_trait]
    /// # impl Service for HttpApiService {
    /// # type Config = HttpApiConfig;
    /// # async fn init(_config: Self::Config) -> Result<Self, Box<dyn std::error::Error>> {
    /// #     Ok(Self)
    /// # }
    /// # async fn start(&mut self) -> Result<(), Box<dyn std::error::Error>> { Ok(()) }
    /// # async fn stop(&mut self) -> Result<(), Box<dyn std::error::Error>> { Ok(()) }
    /// fn name(&self) -> &str {
    ///     "http_api"
    /// }
    /// # }
    /// ```
    fn name(&self) -> &str;
}