Skip to main content

backbone_core/
builder.rs

1//! Builder Pattern Dependency Injection
2//!
3//! This module provides the core components for flexible dependency injection
4//! using the Builder pattern, allowing modules to be composed and configured
5//! in a clean, type-safe manner.
6
7use std::collections::HashMap;
8use std::sync::Arc;
9use async_trait::async_trait;
10use axum::Router;
11use anyhow::Result;
12
13#[cfg(feature = "database")]
14use sqlx::PgPool;
15
16/// Errors that can occur during module operations
17#[derive(Debug, thiserror::Error)]
18pub enum ModuleError {
19    #[error("Module initialization failed: {0}")]
20    InitializationFailed(String),
21    #[error("Module shutdown failed: {0}")]
22    ShutdownFailed(String),
23    #[error("Dependency injection failed: {0}")]
24    DependencyInjectionFailed(String),
25    #[error("Service not found: {0}")]
26    ServiceNotFound(String),
27}
28
29/// Result type for module operations
30pub type ModuleResult<T> = Result<T, ModuleError>;
31
32/// Trait that all business modules must implement
33///
34/// This trait defines the contract for modules in the Backbone Framework,
35/// enabling modular composition and lifecycle management.
36#[async_trait]
37pub trait Module: Send + Sync {
38    /// Returns the name of the module
39    fn name(&self) -> &'static str;
40
41    /// Returns the version of the module
42    fn version(&self) -> &'static str;
43
44    /// Configures routes for this module
45    ///
46    /// # Arguments
47    /// * `router` - The Axum router to configure routes on
48    ///
49    /// # Returns
50    /// The configured router with module-specific routes added
51    async fn configure_routes(&self, router: Router) -> Router;
52
53    /// Configures services in the dependency injection container
54    ///
55    /// # Arguments
56    /// * `container` - The service container to register services in
57    async fn configure_services(&self, container: &mut ServiceContainer);
58
59    /// Initializes the module
60    ///
61    /// This is called after all services are configured and before
62    /// the module starts serving requests. Use this for any
63    /// setup work that needs to be done.
64    async fn initialize(&self) -> ModuleResult<()>;
65
66    /// Shuts down the module
67    ///
68    /// This is called when the application is shutting down.
69    /// Use this for cleanup work.
70    async fn shutdown(&self) -> ModuleResult<()>;
71
72    /// Performs a health check for this module
73    ///
74    /// # Returns
75    /// true if the module is healthy, false otherwise
76    async fn health_check(&self) -> bool {
77        true // Default implementation assumes healthy
78    }
79}
80
81/// Dependency injection container for managing services
82///
83/// This container provides a simple way to register and retrieve
84/// services using type erasure. Services are stored as Arc<dyn Any>
85/// and can be retrieved by their type.
86#[derive(Debug, Default)]
87pub struct ServiceContainer {
88    services: HashMap<String, Arc<dyn std::any::Any + Send + Sync>>,
89}
90
91impl ServiceContainer {
92    /// Creates a new service container
93    pub fn new() -> Self {
94        Self {
95            services: HashMap::new(),
96        }
97    }
98
99    /// Registers a service in the container
100    ///
101    /// # Type Parameters
102    /// * `T` - The service type to register
103    ///
104    /// # Arguments
105    /// * `service` - The service instance to register
106    pub fn register<T: 'static + Send + Sync>(&mut self, service: T) {
107        let type_name = std::any::type_name::<T>().to_string();
108        self.services.insert(type_name, Arc::new(service));
109    }
110
111    /// Registers an Arc<T> service in the container
112    ///
113    /// # Type Parameters
114    /// * `T` - The service type to register
115    ///
116    /// # Arguments
117    /// * `service` - The Arc-wrapped service instance to register
118    pub fn register_arc<T: 'static + Send + Sync>(&mut self, service: Arc<T>) {
119        let type_name = std::any::type_name::<T>().to_string();
120        self.services.insert(type_name, service);
121    }
122
123    /// Retrieves a service from the container
124    ///
125    /// # Type Parameters
126    /// * `T` - The service type to retrieve
127    ///
128    /// # Returns
129    /// An Option containing a reference to the service if found
130    pub fn get<T: 'static + Send + Sync>(&self) -> Option<Arc<T>> {
131        let type_name = std::any::type_name::<T>();
132        self.services
133            .get(type_name)
134            .and_then(|service| service.clone().downcast::<T>().ok())
135    }
136
137    /// Retrieves a service from the container, returning an error if not found
138    ///
139    /// # Type Parameters
140    /// * `T` - The service type to retrieve
141    ///
142    /// # Returns
143    /// A Result containing the service if found
144    pub fn require<T: 'static + Send + Sync>(&self) -> ModuleResult<Arc<T>> {
145        self.get::<T>()
146            .ok_or_else(|| ModuleError::ServiceNotFound(std::any::type_name::<T>().to_string()))
147    }
148
149    /// Lists all registered service types
150    pub fn list_services(&self) -> Vec<String> {
151        self.services.keys().cloned().collect()
152    }
153
154    /// Checks if a service type is registered
155    pub fn has<T: 'static + Send + Sync>(&self) -> bool {
156        let type_name = std::any::type_name::<T>();
157        self.services.contains_key(type_name)
158    }
159}
160
161/// Application structure that holds modules and dependencies
162///
163/// This struct represents the main application with all its
164/// modules, services, and database connections configured.
165pub struct Application {
166    modules: Vec<Box<dyn Module>>,
167    service_container: ServiceContainer,
168    #[cfg(feature = "database")]
169    database_pool: Option<PgPool>,
170    router: Router,
171}
172
173impl Application {
174    /// Gets a reference to the service container
175    pub fn service_container(&self) -> &ServiceContainer {
176        &self.service_container
177    }
178
179    /// Gets a reference to the database pool
180    #[cfg(feature = "database")]
181    pub fn database_pool(&self) -> Option<&PgPool> {
182        self.database_pool.as_ref()
183    }
184
185    /// Gets a reference to the configured router
186    pub fn router(&self) -> &Router {
187        &self.router
188    }
189
190    /// Performs health checks on all modules
191    pub async fn health_check(&self) -> HashMap<String, bool> {
192        let mut results = HashMap::new();
193
194        for module in &self.modules {
195            let healthy = module.health_check().await;
196            results.insert(module.name().to_string(), healthy);
197        }
198
199        results
200    }
201
202    /// Runs the application on the specified address
203    ///
204    /// This starts the Axum HTTP server and blocks until shutdown.
205    ///
206    /// # Arguments
207    /// * `addr` - The socket address to bind to (e.g., "0.0.0.0:3000")
208    ///
209    /// # Example
210    /// ```ignore
211    /// let app = ApplicationBuilder::new()
212    ///     .with_module(my_module)
213    ///     .build()
214    ///     .await?;
215    ///
216    /// app.run("0.0.0.0:3000").await?;
217    /// ```
218    pub async fn run(self, addr: &str) -> ModuleResult<()> {
219        let listener = tokio::net::TcpListener::bind(addr)
220            .await
221            .map_err(|e| ModuleError::InitializationFailed(format!("Failed to bind to {}: {}", addr, e)))?;
222
223        tracing::info!("Starting server on {}", addr);
224
225        axum::serve(listener, self.router)
226            .await
227            .map_err(|e| ModuleError::InitializationFailed(format!("Server error: {}", e)))?;
228
229        Ok(())
230    }
231
232    /// Runs the application with graceful shutdown support
233    ///
234    /// This starts the Axum HTTP server and handles graceful shutdown
235    /// when a shutdown signal is received.
236    ///
237    /// # Arguments
238    /// * `addr` - The socket address to bind to
239    /// * `shutdown_signal` - A future that completes when shutdown is requested
240    ///
241    /// # Example
242    /// ```ignore
243    /// let app = ApplicationBuilder::new()
244    ///     .with_module(my_module)
245    ///     .build()
246    ///     .await?;
247    ///
248    /// app.run_with_shutdown("0.0.0.0:3000", async {
249    ///     tokio::signal::ctrl_c().await.ok();
250    /// }).await?;
251    /// ```
252    pub async fn run_with_shutdown<F>(self, addr: &str, shutdown_signal: F) -> ModuleResult<()>
253    where
254        F: std::future::Future<Output = ()> + Send + 'static,
255    {
256        let listener = tokio::net::TcpListener::bind(addr)
257            .await
258            .map_err(|e| ModuleError::InitializationFailed(format!("Failed to bind to {}: {}", addr, e)))?;
259
260        tracing::info!("Starting server on {} (with graceful shutdown)", addr);
261
262        axum::serve(listener, self.router)
263            .with_graceful_shutdown(shutdown_signal)
264            .await
265            .map_err(|e| ModuleError::InitializationFailed(format!("Server error: {}", e)))?;
266
267        // Shutdown all modules
268        for module in &self.modules {
269            if let Err(e) = module.shutdown().await {
270                tracing::warn!("Module {} shutdown error: {}", module.name(), e);
271            }
272        }
273
274        tracing::info!("Server shutdown complete");
275        Ok(())
276    }
277
278    /// Returns the configured router for use with custom server setup
279    ///
280    /// Use this when you need more control over the server configuration.
281    pub fn into_router(self) -> Router {
282        self.router
283    }
284}
285
286/// Builder for creating Application instances
287///
288/// This builder provides a fluent API for configuring and building
289/// applications with their modules and dependencies.
290pub struct ApplicationBuilder {
291    modules: Vec<Box<dyn Module>>,
292    #[cfg(feature = "database")]
293    database_pool: Option<PgPool>,
294    service_container: ServiceContainer,
295}
296
297impl ApplicationBuilder {
298    /// Creates a new application builder
299    pub fn new() -> Self {
300        Self {
301            modules: Vec::new(),
302            #[cfg(feature = "database")]
303            database_pool: None,
304            service_container: ServiceContainer::new(),
305        }
306    }
307
308    /// Adds a database connection pool to the application
309    ///
310    /// # Arguments
311    /// * `pool` - The PostgreSQL connection pool
312    #[cfg(feature = "database")]
313    pub fn with_database(mut self, pool: PgPool) -> Self {
314        self.database_pool = Some(pool);
315        self
316    }
317
318    /// Adds a module to the application
319    ///
320    /// # Type Parameters
321    /// * `M` - The module type (must implement Module)
322    ///
323    /// # Arguments
324    /// * `module` - The module instance to add
325    pub fn with_module<M: Module + 'static>(mut self, module: M) -> Self {
326        self.modules.push(Box::new(module));
327        self
328    }
329
330    /// Adds a service to the application's service container
331    ///
332    /// # Type Parameters
333    /// * `T` - The service type
334    ///
335    /// # Arguments
336    /// * `service` - The service instance
337    pub fn with_service<T: 'static + Send + Sync>(mut self, service: T) -> Self {
338        self.service_container.register(service);
339        self
340    }
341
342    /// Adds an Arc-wrapped service to the application's service container
343    ///
344    /// # Type Parameters
345    /// * `T` - The service type
346    ///
347    /// # Arguments
348    /// * `service` - The Arc-wrapped service instance
349    pub fn with_service_arc<T: 'static + Send + Sync>(mut self, service: Arc<T>) -> Self {
350        self.service_container.register_arc(service);
351        self
352    }
353
354    /// Builds the application
355    ///
356    /// This method:
357    /// 1. Configures services for all modules
358    /// 2. Initializes all modules
359    /// 3. Configures routes from all modules
360    ///
361    /// # Returns
362    /// A Result containing the built application
363    pub async fn build(self) -> ModuleResult<Application> {
364        let mut service_container = self.service_container;
365        let mut router = Router::new();
366
367        // Configure services for all modules
368        for module in &self.modules {
369            module.configure_services(&mut service_container).await;
370        }
371
372        // Initialize all modules
373        for module in &self.modules {
374            module.initialize().await?;
375        }
376
377        // Configure routes from all modules
378        for module in &self.modules {
379            router = module.configure_routes(router).await;
380        }
381
382        Ok(Application {
383            modules: self.modules,
384            service_container,
385            #[cfg(feature = "database")]
386            database_pool: self.database_pool,
387            router,
388        })
389    }
390}
391
392impl Default for ApplicationBuilder {
393    fn default() -> Self {
394        Self::new()
395    }
396}
397
398#[cfg(test)]
399mod tests {
400    use super::*;
401    use std::sync::atomic::{AtomicU32, Ordering};
402
403    struct TestService {
404        counter: Arc<AtomicU32>,
405    }
406
407    struct TestModule {
408        name: &'static str,
409        version: &'static str,
410    }
411
412    #[async_trait]
413    impl Module for TestModule {
414        fn name(&self) -> &'static str {
415            self.name
416        }
417
418        fn version(&self) -> &'static str {
419            self.version
420        }
421
422        async fn configure_routes(&self, router: Router) -> Router {
423            router
424        }
425
426        async fn configure_services(&self, container: &mut ServiceContainer) {
427            // Configure test services
428        }
429
430        async fn initialize(&self) -> ModuleResult<()> {
431            Ok(())
432        }
433
434        async fn shutdown(&self) -> ModuleResult<()> {
435            Ok(())
436        }
437    }
438
439    #[tokio::test]
440    async fn test_service_container() {
441        let mut container = ServiceContainer::new();
442
443        // Register a service
444        container.register("test".to_string());
445
446        // Retrieve the service
447        let service: Option<Arc<String>> = container.get();
448        assert!(service.is_some());
449        assert_eq!(service.unwrap().as_str(), "test");
450    }
451
452    #[tokio::test]
453    async fn test_application_builder() {
454        let module = TestModule {
455            name: "test",
456            version: "1.0.0",
457        };
458
459        let builder = ApplicationBuilder::new()
460            .with_module(module);
461
462        let app = builder.build().await;
463        assert!(app.is_ok());
464
465        let app = app.unwrap();
466        assert_eq!(app.modules.len(), 1);
467    }
468}