Skip to main content

backbone_core/
module_registry.rs

1//! Module Registry - Discovery and management of Backbone modules
2//!
3//! The Module Registry provides centralized registration of modules,
4//! automatic migration discovery, and dependency-ordered initialization.
5//!
6//! # Architecture
7//!
8//! ```text
9//!                    ┌────────────────────────────┐
10//!                    │      ModuleRegistry        │
11//!                    │   (Application Bootstrap)  │
12//!                    └────────────────────────────┘
13//!                              │
14//!              ┌───────────────┼───────────────┐
15//!              │               │               │
16//!              ▼               ▼               ▼
17//!     ┌────────────┐  ┌────────────┐  ┌────────────┐
18//!     │  Sapiens   │  │  Bersihir  │  │  Bucket    │
19//!     │  Module    │  │  Module    │  │  Module    │
20//!     └────────────┘  └────────────┘  └────────────┘
21//! ```
22//!
23//! # Usage
24//!
25//! ```rust,ignore
26//! use backbone_core::module_registry::ModuleRegistry;
27//! use backbone_core::module::BackboneModule;
28//!
29//! // Create registry
30//! let mut registry = ModuleRegistry::new();
31//!
32//! // Register modules
33//! registry.register(SapiensModule::new());
34//! registry.register(BersihirModule::new());
35//!
36//! // Get modules in dependency order
37//! for module in registry.modules_ordered() {
38//!     // Run migrations, initialize, etc.
39//! }
40//!
41//! // Get all migration paths
42//! for (name, path) in registry.all_migration_paths() {
43//!     println!("Module {}: {}", name, path.display());
44//! }
45//! ```
46
47use std::collections::{HashMap, HashSet};
48use std::sync::Arc;
49
50use crate::module::{BackboneModule, MigrationInfo, SeedInfo};
51
52/// Error types for module registry operations
53#[derive(Debug, thiserror::Error)]
54pub enum ModuleRegistryError {
55    #[error("Circular dependency detected: {0}")]
56    CircularDependency(String),
57
58    #[error("Unknown dependency '{dependency}' for module '{module}'")]
59    UnknownDependency {
60        module: String,
61        dependency: String,
62    },
63
64    #[error("Module '{0}' is already registered")]
65    DuplicateModule(String),
66}
67
68/// Result type for module registry operations
69pub type ModuleRegistryResult<T> = Result<T, ModuleRegistryError>;
70
71/// Registry for managing Backbone modules
72///
73/// The registry handles:
74/// - Module registration
75/// - Dependency resolution and ordering
76/// - Migration path discovery
77/// - Module lifecycle (boot, shutdown)
78pub struct ModuleRegistry {
79    modules: HashMap<String, Arc<dyn BackboneModule>>,
80    order: Option<Vec<String>>,
81}
82
83impl ModuleRegistry {
84    /// Creates a new empty module registry
85    pub fn new() -> Self {
86        Self {
87            modules: HashMap::new(),
88            order: None,
89        }
90    }
91
92    /// Registers a module with the registry
93    ///
94    /// # Arguments
95    /// * `module` - The module to register
96    ///
97    /// # Returns
98    /// An error if a module with the same name is already registered
99    pub fn register<M: BackboneModule + 'static>(&mut self, module: M) -> ModuleRegistryResult<()> {
100        let name = module.name().to_string();
101
102        if self.modules.contains_key(&name) {
103            return Err(ModuleRegistryError::DuplicateModule(name));
104        }
105
106        self.modules.insert(name, Arc::new(module));
107        self.order = None; // Invalidate cached order
108        Ok(())
109    }
110
111    /// Registers an Arc-wrapped module with the registry
112    pub fn register_arc(&mut self, module: Arc<dyn BackboneModule>) -> ModuleRegistryResult<()> {
113        let name = module.name().to_string();
114
115        if self.modules.contains_key(&name) {
116            return Err(ModuleRegistryError::DuplicateModule(name));
117        }
118
119        self.modules.insert(name, module);
120        self.order = None;
121        Ok(())
122    }
123
124    /// Returns the number of registered modules
125    pub fn len(&self) -> usize {
126        self.modules.len()
127    }
128
129    /// Returns true if no modules are registered
130    pub fn is_empty(&self) -> bool {
131        self.modules.is_empty()
132    }
133
134    /// Gets a module by name
135    pub fn get(&self, name: &str) -> Option<Arc<dyn BackboneModule>> {
136        self.modules.get(name).cloned()
137    }
138
139    /// Returns all module names
140    pub fn module_names(&self) -> Vec<String> {
141        self.modules.keys().cloned().collect()
142    }
143
144    /// Returns all modules in dependency order
145    ///
146    /// Modules with no dependencies come first, followed by modules
147    /// whose dependencies have already been returned.
148    ///
149    /// # Returns
150    /// An error if there are circular dependencies or unknown dependencies
151    pub fn modules_ordered(&mut self) -> ModuleRegistryResult<Vec<Arc<dyn BackboneModule>>> {
152        // Compute order if not cached
153        if self.order.is_none() {
154            self.order = Some(self.topological_sort()?);
155        }
156
157        let order = self.order.as_ref().unwrap();
158        Ok(order
159            .iter()
160            .filter_map(|name| self.modules.get(name).cloned())
161            .collect())
162    }
163
164    /// Returns all migration paths in dependency order
165    ///
166    /// # Returns
167    /// A vector of (module_name, migrations_path) tuples
168    pub fn all_migration_paths(&mut self) -> ModuleRegistryResult<Vec<MigrationInfo>> {
169        let modules = self.modules_ordered()?;
170        Ok(modules
171            .iter()
172            .filter_map(|m| m.migration_info())
173            .collect())
174    }
175
176    /// Returns all seed paths in dependency order
177    pub fn all_seed_paths(&mut self) -> ModuleRegistryResult<Vec<SeedInfo>> {
178        let modules = self.modules_ordered()?;
179        Ok(modules
180            .iter()
181            .filter_map(|m| m.seed_info())
182            .collect())
183    }
184
185    /// Performs topological sort of modules based on dependencies
186    fn topological_sort(&self) -> ModuleRegistryResult<Vec<String>> {
187        let mut result = Vec::new();
188        let mut visited = HashSet::new();
189        let mut in_progress = HashSet::new();
190
191        // Build dependency graph
192        let module_names: HashSet<_> = self.modules.keys().cloned().collect();
193
194        // Validate all dependencies exist
195        for module in self.modules.values() {
196            for dep in module.dependencies() {
197                if !module_names.contains(dep) {
198                    return Err(ModuleRegistryError::UnknownDependency {
199                        module: module.name().to_string(),
200                        dependency: dep.to_string(),
201                    });
202                }
203            }
204        }
205
206        // Visit each module
207        for name in self.modules.keys() {
208            self.visit(name, &mut visited, &mut in_progress, &mut result)?;
209        }
210
211        Ok(result)
212    }
213
214    /// DFS visit for topological sort
215    fn visit(
216        &self,
217        name: &str,
218        visited: &mut HashSet<String>,
219        in_progress: &mut HashSet<String>,
220        result: &mut Vec<String>,
221    ) -> ModuleRegistryResult<()> {
222        if visited.contains(name) {
223            return Ok(());
224        }
225
226        if in_progress.contains(name) {
227            return Err(ModuleRegistryError::CircularDependency(name.to_string()));
228        }
229
230        in_progress.insert(name.to_string());
231
232        // Visit dependencies first
233        if let Some(module) = self.modules.get(name) {
234            for dep in module.dependencies() {
235                self.visit(dep, visited, in_progress, result)?;
236            }
237        }
238
239        in_progress.remove(name);
240        visited.insert(name.to_string());
241        result.push(name.to_string());
242
243        Ok(())
244    }
245
246    /// Boots all modules in dependency order
247    pub async fn boot_all(&mut self) -> anyhow::Result<()> {
248        let modules = self.modules_ordered()?;
249        for module in modules {
250            tracing::info!("Booting module: {} v{}", module.name(), module.version());
251            module.on_boot().await?;
252        }
253        Ok(())
254    }
255
256    /// Shuts down all modules in reverse dependency order
257    pub async fn shutdown_all(&mut self) -> anyhow::Result<()> {
258        let mut modules = self.modules_ordered()?;
259        modules.reverse(); // Shutdown in reverse order
260
261        for module in modules {
262            tracing::info!("Shutting down module: {}", module.name());
263            if let Err(e) = module.on_shutdown().await {
264                tracing::warn!("Error shutting down module {}: {}", module.name(), e);
265            }
266        }
267        Ok(())
268    }
269
270    /// Performs health checks on all modules
271    pub async fn health_check_all(&self) -> HashMap<String, bool> {
272        let mut results = HashMap::new();
273
274        for (name, module) in &self.modules {
275            let healthy = module.health_check().await;
276            results.insert(name.clone(), healthy);
277        }
278
279        results
280    }
281
282    /// Prints a summary of registered modules
283    #[allow(clippy::print_literal)]
284    pub fn print_summary(&self) {
285        println!("\n📦 Registered Modules:");
286        println!("  {0:<20} {1:<12} {2}", "Name", "Version", "Dependencies");
287        println!("  {}", "-".repeat(60));
288
289        for (name, module) in &self.modules {
290            let deps = module.dependencies();
291            let deps_str = if deps.is_empty() {
292                "-".to_string()
293            } else {
294                deps.join(", ")
295            };
296            println!("  {:<20} {:<12} {}", name, module.version(), deps_str);
297        }
298        println!();
299    }
300}
301
302impl Default for ModuleRegistry {
303    fn default() -> Self {
304        Self::new()
305    }
306}
307
308#[cfg(test)]
309mod tests {
310    use super::*;
311    use crate::module::BackboneModule;
312    use std::path::PathBuf;
313
314    struct TestModuleA;
315    struct TestModuleB;
316    struct TestModuleC;
317
318    impl BackboneModule for TestModuleA {
319        fn name(&self) -> &'static str { "module_a" }
320        fn version(&self) -> &'static str { "1.0.0" }
321        fn dependencies(&self) -> Vec<&'static str> { vec![] }
322        fn migrations_path(&self) -> Option<PathBuf> {
323            Some(PathBuf::from("libs/modules/a/migrations"))
324        }
325    }
326
327    impl BackboneModule for TestModuleB {
328        fn name(&self) -> &'static str { "module_b" }
329        fn version(&self) -> &'static str { "1.0.0" }
330        fn dependencies(&self) -> Vec<&'static str> { vec!["module_a"] }
331        fn migrations_path(&self) -> Option<PathBuf> {
332            Some(PathBuf::from("libs/modules/b/migrations"))
333        }
334    }
335
336    impl BackboneModule for TestModuleC {
337        fn name(&self) -> &'static str { "module_c" }
338        fn version(&self) -> &'static str { "1.0.0" }
339        fn dependencies(&self) -> Vec<&'static str> { vec!["module_a", "module_b"] }
340        fn migrations_path(&self) -> Option<PathBuf> {
341            Some(PathBuf::from("libs/modules/c/migrations"))
342        }
343    }
344
345    #[test]
346    fn test_register_modules() {
347        let mut registry = ModuleRegistry::new();
348
349        assert!(registry.register(TestModuleA).is_ok());
350        assert!(registry.register(TestModuleB).is_ok());
351
352        assert_eq!(registry.len(), 2);
353    }
354
355    #[test]
356    fn test_duplicate_module() {
357        let mut registry = ModuleRegistry::new();
358
359        assert!(registry.register(TestModuleA).is_ok());
360
361        // Attempting to register again should fail
362        let result = registry.register(TestModuleA);
363        assert!(result.is_err());
364    }
365
366    #[test]
367    fn test_dependency_order() {
368        let mut registry = ModuleRegistry::new();
369
370        // Register in reverse dependency order
371        registry.register(TestModuleC).unwrap();
372        registry.register(TestModuleB).unwrap();
373        registry.register(TestModuleA).unwrap();
374
375        let ordered = registry.modules_ordered().unwrap();
376        let names: Vec<_> = ordered.iter().map(|m| m.name()).collect();
377
378        // A should come before B, B should come before C
379        let pos_a = names.iter().position(|&n| n == "module_a").unwrap();
380        let pos_b = names.iter().position(|&n| n == "module_b").unwrap();
381        let pos_c = names.iter().position(|&n| n == "module_c").unwrap();
382
383        assert!(pos_a < pos_b);
384        assert!(pos_b < pos_c);
385    }
386
387    #[test]
388    fn test_migration_paths() {
389        let mut registry = ModuleRegistry::new();
390
391        registry.register(TestModuleA).unwrap();
392        registry.register(TestModuleB).unwrap();
393        registry.register(TestModuleC).unwrap();
394
395        let paths = registry.all_migration_paths().unwrap();
396        assert_eq!(paths.len(), 3);
397
398        // First should be module_a
399        assert_eq!(paths[0].module, "module_a");
400    }
401
402    struct CircularA;
403    struct CircularB;
404
405    impl BackboneModule for CircularA {
406        fn name(&self) -> &'static str { "circular_a" }
407        fn version(&self) -> &'static str { "1.0.0" }
408        fn dependencies(&self) -> Vec<&'static str> { vec!["circular_b"] }
409        fn migrations_path(&self) -> Option<PathBuf> { None }
410    }
411
412    impl BackboneModule for CircularB {
413        fn name(&self) -> &'static str { "circular_b" }
414        fn version(&self) -> &'static str { "1.0.0" }
415        fn dependencies(&self) -> Vec<&'static str> { vec!["circular_a"] }
416        fn migrations_path(&self) -> Option<PathBuf> { None }
417    }
418
419    #[test]
420    fn test_circular_dependency_detection() {
421        let mut registry = ModuleRegistry::new();
422
423        registry.register(CircularA).unwrap();
424        registry.register(CircularB).unwrap();
425
426        let result = registry.modules_ordered();
427        assert!(result.is_err());
428
429        if let Err(ModuleRegistryError::CircularDependency(_)) = result {
430            // Expected
431        } else {
432            panic!("Expected CircularDependency error");
433        }
434    }
435
436    #[test]
437    fn test_unknown_dependency() {
438        let mut registry = ModuleRegistry::new();
439
440        // Register B without A (B depends on A)
441        registry.register(TestModuleB).unwrap();
442
443        let result = registry.modules_ordered();
444        assert!(result.is_err());
445
446        if let Err(ModuleRegistryError::UnknownDependency { module, dependency }) = result {
447            assert_eq!(module, "module_b");
448            assert_eq!(dependency, "module_a");
449        } else {
450            panic!("Expected UnknownDependency error");
451        }
452    }
453}