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}