dmsc/core/app_builder.rs
1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of DMSC.
4//! The DMSC project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! you may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//! http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18#![allow(non_snake_case)]
19
20//! # Application Builder
21//!
22//! This module provides the application builder for constructing DMSC applications.
23//! The `DMSCAppBuilder` follows the builder pattern for fluent configuration,
24//! enabling developers to compose applications from various modules, configuration
25//! sources, and runtime settings in a declarative manner.
26//!
27//! ## Builder Pattern
28//!
29//! The builder pattern allows step-by-step construction of complex objects.
30//! Each method on `DMSCAppBuilder` configures a specific aspect of the application
31//! and returns the builder for method chaining. This results in a fluent API
32//! that is both readable and type-safe.
33//!
34//! ## Module Registration
35//!
36//! Modules are the primary extension point for DMSC applications. The builder
37//! supports multiple types of modules:
38//!
39//! - **Synchronous modules**: Implement `ServiceModule` trait for sync operations
40//! - **Asynchronous modules**: Implement `AsyncServiceModule` trait for async operations
41//! - **DMSC modules**: Implement public `DMSCModule` trait (converted to async internally)
42//! - **Python modules**: Modules created from Python code (with pyo3 feature)
43//!
44//! ## Configuration Management
45//!
46//! The builder supports multiple configuration sources with a defined priority order:
47//!
48//! 1. Configuration files (lowest priority): `dms.yaml`, `dms.yml`, `dms.toml`, `dms.json`
49//! 2. Custom configuration via `with_config()` method
50//! 3. Environment variables (highest priority)
51//!
52//! ## Usage Example
53//!
54//! ```rust
55//! use dmsc::prelude::*;
56//!
57//! #[tokio::main]
58//! async fn main() -> DMSCResult<()> {
59//! let app = DMSCAppBuilder::new()
60//! .with_config("config.yaml")?
61//! .with_module(Box::new(MySyncModule::new()))
62//! .with_async_module(Box::new(MyAsyncModule::new()))
63//! .build()?;
64//!
65//! app.run(|ctx| async move {
66//! ctx.logger().info("service", "DMSC service started")?;
67//! Ok(())
68//! }).await
69//! }
70//! ```
71//!
72//! ## Thread Safety
73//!
74//! The `DMSCAppBuilder` is designed to be used in a single-threaded context
75//! during application construction. After calling `build()`, the resulting
76//! `DMSCAppRuntime` is safe to use across multiple threads.
77//!
78//! ## Error Handling
79//!
80//! All builder methods that can fail return `DMSCResult`, enabling proper
81//! error handling through the `?` operator or explicit match statements.
82
83use crate::core::{DMSCResult, DMSCServiceContext, ServiceModule, AsyncServiceModule};
84use super::module_sorter::sort_modules;
85use super::module_types::{ModuleSlot, ModuleType};
86use super::lifecycle::DMSCLifecycleObserver;
87use super::analytics::DMSCLogAnalyticsModule;
88use std::sync::Arc;
89
90#[cfg(feature = "pyo3")]
91use pyo3::PyResult;
92#[cfg(feature = "pyo3")]
93use crate::core::app_runtime::DMSCAppRuntime;
94
95/// Public-facing application builder for DMSC.
96///
97/// The `DMSCAppBuilder` provides a fluent API for configuring and building DMSC applications.
98/// It follows the builder pattern, allowing users to configure various aspects of the application
99/// before building the final runtime.
100///
101/// ## Usage
102///
103/// ```rust
104/// use dmsc::prelude::*;
105///
106/// #[tokio::main]
107/// async fn main() -> DMSCResult<()> {
108/// let app = DMSCAppBuilder::new()
109/// .with_config("config.yaml")?
110/// .with_module(Box::new(MySyncModule::new()))
111/// .with_async_module(Box::new(MyAsyncModule::new()))
112/// .build()?;
113///
114/// app.run(|ctx| async move {
115/// ctx.logger().info("service", "DMSC service started")?;
116/// Ok(())
117/// }).await
118/// }
119/// ```
120
121#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
122pub struct DMSCAppBuilder {
123 /// Vector of modules with their state, including both sync and async modules
124 modules: Vec<ModuleSlot>,
125 /// Configuration file paths to load
126 config_paths: Vec<String>,
127 /// Custom logging configuration (optional)
128 logging_config: Option<crate::log::DMSCLogConfig>,
129 /// Custom observability configuration (optional)
130 observability_config: Option<crate::observability::DMSCObservabilityConfig>,
131}
132
133impl Default for DMSCAppBuilder {
134 fn default() -> Self {
135 Self::new()
136 }
137}
138
139impl DMSCAppBuilder {
140 /// Create a new empty application builder.
141 ///
142 /// # Returns
143 ///
144 /// A new `DMSCAppBuilder` instance with default settings.
145 pub fn new() -> Self {
146 DMSCAppBuilder {
147 modules: Vec::new(),
148 config_paths: Vec::new(),
149 logging_config: None,
150 observability_config: None,
151 }
152 }
153
154 /// Add a synchronous module to the application.
155 ///
156 /// # Parameters
157 ///
158 /// - `module`: A boxed synchronous module implementing `ServiceModule`
159 ///
160 /// # Returns
161 ///
162 /// The updated `DMSCAppBuilder` instance for method chaining.
163 pub fn with_module(mut self, module: Box<dyn ServiceModule>) -> Self {
164 self.modules.push(ModuleSlot { module: ModuleType::Sync(module), failed: false });
165 self
166 }
167
168 /// Add a Python module to the application.
169 ///
170 /// This method adds a module created from Python code to the application.
171 /// The module will be treated as an asynchronous DMSC module.
172 ///
173 /// # Parameters
174 ///
175 /// - `module`: A Python module adapter implementing module configuration
176 ///
177 /// # Returns
178 ///
179 /// The updated `DMSCAppBuilder` instance for method chaining.
180 #[cfg(feature = "pyo3")]
181 pub fn with_python_module(mut self, module: crate::core::module::DMSCPythonModuleAdapter) -> Self {
182 self.modules.push(ModuleSlot {
183 module: ModuleType::Async(Box::new(module)),
184 failed: false
185 });
186 self
187 }
188
189 /// Add an asynchronous module to the application.
190 ///
191 /// # Parameters
192 ///
193 /// - `module`: A boxed asynchronous module implementing `AsyncServiceModule`
194 ///
195 /// # Returns
196 ///
197 /// The updated `DMSCAppBuilder` instance for method chaining.
198 pub fn with_async_module(mut self, module: Box<dyn AsyncServiceModule>) -> Self {
199 self.modules.push(ModuleSlot { module: ModuleType::Async(module), failed: false });
200 self
201 }
202
203 /// Add a DMSC module to the application.
204 ///
205 /// This method adds a module implementing the public `DMSCModule` trait to the application.
206 /// The module will be treated as an asynchronous module.
207 ///
208 /// # Parameters
209 ///
210 /// - `module`: A boxed module implementing `DMSCModule`
211 ///
212 /// # Returns
213 ///
214 /// The updated `DMSCAppBuilder` instance for method chaining.
215 pub fn with_dms_module(mut self, module: Box<dyn crate::core::DMSCModule>) -> Self {
216 // Wrap DMSCModule into AsyncServiceModule adapter
217 struct DMSCModuleAdapter(Box<dyn crate::core::DMSCModule + Send + Sync + 'static>);
218
219 #[async_trait::async_trait]
220 impl AsyncServiceModule for DMSCModuleAdapter {
221 fn name(&self) -> &str {
222 self.0.name()
223 }
224
225 fn is_critical(&self) -> bool {
226 self.0.is_critical()
227 }
228
229 fn priority(&self) -> i32 {
230 self.0.priority()
231 }
232
233 fn dependencies(&self) -> Vec<&str> {
234 self.0.dependencies()
235 }
236
237 async fn init(&mut self, ctx: &mut DMSCServiceContext) -> DMSCResult<()> {
238 self.0.init(ctx).await
239 }
240
241 async fn before_start(&mut self, ctx: &mut DMSCServiceContext) -> DMSCResult<()> {
242 self.0.before_start(ctx).await
243 }
244
245 async fn start(&mut self, ctx: &mut DMSCServiceContext) -> DMSCResult<()> {
246 self.0.start(ctx).await
247 }
248
249 async fn after_start(&mut self, ctx: &mut DMSCServiceContext) -> DMSCResult<()> {
250 self.0.after_start(ctx).await
251 }
252
253 async fn before_shutdown(&mut self, ctx: &mut DMSCServiceContext) -> DMSCResult<()> {
254 self.0.before_shutdown(ctx).await
255 }
256
257 async fn shutdown(&mut self, ctx: &mut DMSCServiceContext) -> DMSCResult<()> {
258 self.0.shutdown(ctx).await
259 }
260
261 async fn after_shutdown(&mut self, ctx: &mut DMSCServiceContext) -> DMSCResult<()> {
262 self.0.after_shutdown(ctx).await
263 }
264 }
265
266 self.modules.push(ModuleSlot {
267 module: ModuleType::Async(Box::new(DMSCModuleAdapter(module))),
268 failed: false
269 });
270 self
271 }
272
273 /// Add multiple synchronous modules to the application.
274 ///
275 /// # Parameters
276 ///
277 /// - `modules`: A vector of boxed synchronous modules implementing `ServiceModule`
278 ///
279 /// # Returns
280 ///
281 /// The updated `DMSCAppBuilder` instance for method chaining.
282 pub fn with_modules(mut self, modules: Vec<Box<dyn ServiceModule>>) -> Self {
283 for module in modules {
284 self.modules.push(ModuleSlot { module: ModuleType::Sync(module), failed: false });
285 }
286 self
287 }
288
289 /// Add multiple asynchronous modules to the application.
290 ///
291 /// # Parameters
292 ///
293 /// - `modules`: A vector of boxed asynchronous modules implementing `AsyncServiceModule`
294 ///
295 /// # Returns
296 ///
297 /// The updated `DMSCAppBuilder` instance for method chaining.
298 pub fn with_async_modules(mut self, modules: Vec<Box<dyn AsyncServiceModule>>) -> Self {
299 for module in modules {
300 self.modules.push(ModuleSlot { module: ModuleType::Async(module), failed: false });
301 }
302 self
303 }
304
305 /// Add multiple DMSC modules to the application.
306 ///
307 /// This method adds multiple modules implementing the public `DMSCModule` trait to the application.
308 /// Each module will be treated as an asynchronous module.
309 ///
310 /// # Parameters
311 ///
312 /// - `modules`: A vector of boxed modules implementing `DMSCModule`
313 ///
314 /// # Returns
315 ///
316 /// The updated `DMSCAppBuilder` instance for method chaining.
317 pub fn with_dms_modules(mut self, modules: Vec<Box<dyn crate::core::DMSCModule>>) -> Self {
318 for module in modules {
319 self = self.with_dms_module(module);
320 }
321 self
322 }
323
324 /// Add a configuration file to the application.
325 ///
326 /// # Parameters
327 ///
328 /// - `config_path`: Path to the configuration file
329 ///
330 /// # Returns
331 ///
332 /// A `DMSCResult` containing the updated `DMSCAppBuilder` instance for method chaining.
333 ///
334 /// # Errors
335 ///
336 /// This method currently never returns an error, but returns `DMSCResult` for consistency
337 /// with other builder methods and to allow for future error handling.
338 pub fn with_config(mut self, config_path: impl Into<String>) -> DMSCResult<Self> {
339 self.config_paths.push(config_path.into());
340 Ok(self)
341 }
342
343 /// Set custom logging configuration for the application.
344 ///
345 /// # Parameters
346 ///
347 /// - `logging_config`: Custom logging configuration
348 ///
349 /// # Returns
350 ///
351 /// The updated `DMSCAppBuilder` instance for method chaining.
352 pub fn with_logging(mut self, logging_config: crate::log::DMSCLogConfig) -> Self {
353 self.logging_config = Some(logging_config);
354 self
355 }
356
357 /// Set custom observability configuration for the application.
358 ///
359 /// # Parameters
360 ///
361 /// - `observability_config`: Custom observability configuration
362 ///
363 /// # Returns
364 ///
365 /// The updated `DMSCAppBuilder` instance for method chaining.
366 pub fn with_observability(mut self, observability_config: crate::observability::DMSCObservabilityConfig) -> Self {
367 self.observability_config = Some(observability_config);
368 self
369 }
370
371 /// Add cache module with configuration.
372 ///
373 /// This method adds a cache module to the application with custom configuration.
374 /// The configuration is provided via a closure that receives a cache config builder.
375 ///
376 /// # Parameters
377 ///
378 /// - `config_fn`: Closure for configuring the cache module
379 ///
380 /// # Returns
381 ///
382 /// The updated `DMSCAppBuilder` instance for method chaining.
383 pub fn with_cache_module<F>(mut self, config_fn: F) -> Self
384 where
385 F: FnOnce(&mut crate::cache::DMSCCacheConfig) -> &mut crate::cache::DMSCCacheConfig,
386 {
387 let mut config = crate::cache::DMSCCacheConfig::default();
388 config_fn(&mut config);
389 let cache_module = crate::cache::DMSCCacheModule::with_config(config);
390 self.modules.push(ModuleSlot {
391 module: ModuleType::Sync(Box::new(cache_module)),
392 failed: false,
393 });
394 self
395 }
396
397 /// Add authentication module with configuration.
398 ///
399 /// This method adds an authentication module to the application with custom configuration.
400 ///
401 /// # Parameters
402 ///
403 /// - `config_fn`: Closure for configuring the auth module
404 ///
405 /// # Returns
406 ///
407 /// The updated `DMSCAppBuilder` instance for method chaining.
408 pub fn with_auth_module<F>(mut self, config_fn: F) -> Self
409 where
410 F: FnOnce(&mut crate::auth::DMSCAuthConfig) -> &mut crate::auth::DMSCAuthConfig,
411 {
412 let mut config = crate::auth::DMSCAuthConfig::default();
413 config_fn(&mut config);
414 let auth_module = crate::auth::DMSCAuthModule::with_config(config);
415 self.modules.push(ModuleSlot {
416 module: ModuleType::Sync(Box::new(auth_module)),
417 failed: false,
418 });
419 self
420 }
421
422 /// Add queue module with configuration.
423 ///
424 /// This method adds a message queue module to the application with custom configuration.
425 ///
426 /// # Parameters
427 ///
428 /// - `config_fn`: Closure for configuring the queue module
429 ///
430 /// # Returns
431 ///
432 /// The updated `DMSCAppBuilder` instance for method chaining.
433 pub fn with_queue_module<F>(mut self, config_fn: F) -> Self
434 where
435 F: FnOnce(&mut crate::queue::DMSCQueueConfig) -> &mut crate::queue::DMSCQueueConfig,
436 {
437 let mut config = crate::queue::DMSCQueueConfig::default();
438 config_fn(&mut config);
439 match crate::queue::DMSCQueueModule::with_config(config) {
440 Ok(queue_module) => {
441 self.modules.push(ModuleSlot {
442 module: ModuleType::Sync(Box::new(queue_module)),
443 failed: false,
444 });
445 }
446 Err(e) => {
447 log::error!("Failed to create queue module: {}", e);
448 }
449 }
450 self
451 }
452
453 /// Add device control module with configuration.
454 ///
455 /// This method adds a device control module to the application with custom configuration.
456 ///
457 /// # Parameters
458 ///
459 /// - `config_fn`: Closure for configuring the device module
460 ///
461 /// # Returns
462 ///
463 /// The updated `DMSCAppBuilder` instance for method chaining.
464 pub fn with_device_module<F>(mut self, config_fn: F) -> Self
465 where
466 F: FnOnce(&mut crate::device::DMSCDeviceControlConfig) -> &mut crate::device::DMSCDeviceControlConfig,
467 {
468 let mut config = crate::device::DMSCDeviceControlConfig::default();
469 config_fn(&mut config);
470 let device_module = crate::device::DMSCDeviceControlModule::new().with_config(config);
471 self.modules.push(ModuleSlot {
472 module: ModuleType::Sync(Box::new(device_module)),
473 failed: false,
474 });
475 self
476 }
477
478 /// Build the application runtime.
479 ///
480 /// This method performs the following steps:
481 /// 1. Creates and configures the configuration manager
482 /// 2. Loads configuration from specified files and environment variables
483 /// 3. Creates the service context with core functionalities
484 /// 4. Adds core modules (analytics and lifecycle observer)
485 /// 5. Sorts modules based on dependencies and priority
486 /// 6. Creates and returns the application runtime
487 ///
488 /// # Returns
489 ///
490 /// A `DMSCResult` containing the built `DMSCAppRuntime` instance, or an error if building fails.
491 ///
492 /// # Errors
493 ///
494 /// - If configuration loading fails
495 /// - If service context creation fails
496 /// - If module sorting fails due to circular dependencies
497 pub fn build(mut self) -> DMSCResult<super::app_runtime::DMSCAppRuntime> {
498 // Create config manager with specified config paths
499 let mut config_manager = crate::config::DMSCConfigManager::new();
500
501 // Add specified config files
502 for path in &self.config_paths {
503 config_manager.add_file_source(path);
504 }
505
506 // Add default config sources if no paths specified
507 if self.config_paths.is_empty() {
508 if let Ok(cwd) = std::env::current_dir() {
509 let config_dir = cwd.join("config");
510
511 // Add all supported config files in order of priority (lowest to highest)
512 config_manager.add_file_source(config_dir.join("dms.yaml"));
513 config_manager.add_file_source(config_dir.join("dms.yml"));
514 config_manager.add_file_source(config_dir.join("dms.toml"));
515 config_manager.add_file_source(config_dir.join("dms.json"));
516 }
517 }
518
519 // Add environment variables as highest priority
520 config_manager.add_environment_source();
521
522 // Load configuration
523 config_manager.load()?;
524
525 // Create service context with custom configuration
526 let ctx = self.create_service_context(config_manager)?;
527
528 // Add core modules
529 self.modules.push(ModuleSlot { module: ModuleType::Sync(Box::new(DMSCLogAnalyticsModule::new())), failed: false });
530 self.modules.push(ModuleSlot { module: ModuleType::Sync(Box::new(DMSCLifecycleObserver::new())), failed: false });
531
532 // Sort modules based on dependencies and priority
533 self.modules = sort_modules(self.modules)?;
534
535 let runtime = super::app_runtime::DMSCAppRuntime::new(ctx, self.modules);
536 Ok(runtime)
537 }
538
539 /// Create the service context with the given configuration manager.
540 ///
541 /// This method creates the service context with the following components:
542 /// 1. File system accessor
543 /// 2. Logger (using custom config if provided, otherwise from configuration)
544 /// 3. Configuration manager
545 /// 4. Hook bus for lifecycle events
546 ///
547 /// # Parameters
548 ///
549 /// - `config_manager`: Configuration manager with loaded configuration
550 ///
551 /// # Returns
552 ///
553 /// A `DMSCResult` containing the created `DMSCServiceContext` instance, or an error if creation fails.
554 ///
555 /// # Errors
556 ///
557 /// - If project root directory detection fails
558 /// - If file system creation fails
559 /// - If logger creation fails
560 fn create_service_context(&self, config_manager: crate::config::DMSCConfigManager) -> DMSCResult<DMSCServiceContext> {
561 let cfg = config_manager.config();
562
563 let project_root = std::env::current_dir()
564 .map_err(|e| crate::core::DMSCError::Other(format!("detect project root failed: {e}")))?;
565 let app_data_root = if let Some(root_str) = cfg.get_str("fs.app_data_root") {
566 project_root.join(root_str)
567 } else {
568 project_root.join(".dms")
569 };
570
571 let fs = crate::fs::DMSCFileSystem::new_with_roots(project_root, app_data_root);
572
573 // Use custom logging config if provided, otherwise create from config
574 let log_config: crate::log::DMSCLogConfig = if let Some(log_config) = &self.logging_config {
575 log_config.clone()
576 } else {
577 crate::log::DMSCLogConfig::from_config(&cfg)
578 };
579 let logger = crate::log::DMSCLogger::new(&log_config, fs.clone());
580 let hooks = crate::hooks::DMSCHookBus::new();
581 let metrics_registry = Some(Arc::new(crate::observability::DMSCMetricsRegistry::new()));
582
583 Ok(DMSCServiceContext::new_with(fs, logger, config_manager, hooks, metrics_registry))
584 }
585}
586
587#[cfg(feature = "pyo3")]
588#[pyo3::prelude::pymethods]
589impl DMSCAppBuilder {
590 #[new]
591 fn py_new() -> Self {
592 Self::new()
593 }
594
595 fn py_with_config(&mut self, config_path: &str) -> PyResult<Self> {
596 self.config_paths.push(config_path.to_string());
597 Ok(std::mem::take(self))
598 }
599
600 fn py_with_logging(&mut self, logging_config: crate::log::DMSCLogConfig) -> PyResult<Self> {
601 self.logging_config = Some(logging_config);
602 Ok(std::mem::take(self))
603 }
604
605 fn py_with_observability(&mut self, observability_config: crate::observability::DMSCObservabilityConfig) -> PyResult<Self> {
606 self.observability_config = Some(observability_config);
607 Ok(std::mem::take(self))
608 }
609
610 fn py_build(&mut self) -> PyResult<DMSCAppRuntime> {
611 let builder = std::mem::take(self);
612 DMSCAppBuilder::build(builder).map_err(|e| pyo3::prelude::PyErr::from(e))
613 }
614
615 fn py_with_module(&mut self, module: super::module::DMSCPythonServiceModule) -> PyResult<Self> {
616 self.modules.push(crate::core::module_types::ModuleSlot {
617 module: crate::core::module_types::ModuleType::Sync(Box::new(module)),
618 failed: false,
619 });
620 Ok(std::mem::take(self))
621 }
622
623 fn py_with_python_module(&mut self, module: super::module::DMSCPythonModuleAdapter) -> PyResult<Self> {
624 self.modules.push(crate::core::module_types::ModuleSlot {
625 module: crate::core::module_types::ModuleType::Async(Box::new(module)),
626 failed: false,
627 });
628 Ok(std::mem::take(self))
629 }
630
631 fn py_with_async_module(&mut self, module: super::module::DMSCPythonAsyncServiceModule) -> PyResult<Self> {
632 self.modules.push(crate::core::module_types::ModuleSlot {
633 module: crate::core::module_types::ModuleType::Async(Box::new(module)),
634 failed: false,
635 });
636 Ok(std::mem::take(self))
637 }
638
639 fn py_with_dms_module(&mut self, module: super::module::DMSCPythonModuleAdapter) -> PyResult<Self> {
640 self.modules.push(crate::core::module_types::ModuleSlot {
641 module: crate::core::module_types::ModuleType::Async(Box::new(module)),
642 failed: false,
643 });
644 Ok(std::mem::take(self))
645 }
646
647 fn py_with_modules(&mut self, modules: Vec<super::module::DMSCPythonServiceModule>) -> PyResult<Self> {
648 for module in modules {
649 self.modules.push(crate::core::module_types::ModuleSlot {
650 module: crate::core::module_types::ModuleType::Sync(Box::new(module)),
651 failed: false,
652 });
653 }
654 Ok(std::mem::take(self))
655 }
656
657 fn py_with_async_modules(&mut self, modules: Vec<super::module::DMSCPythonAsyncServiceModule>) -> PyResult<Self> {
658 for module in modules {
659 self.modules.push(crate::core::module_types::ModuleSlot {
660 module: crate::core::module_types::ModuleType::Async(Box::new(module)),
661 failed: false,
662 });
663 }
664 Ok(std::mem::take(self))
665 }
666
667 fn py_with_dms_modules(&mut self, modules: Vec<super::module::DMSCPythonModuleAdapter>) -> PyResult<Self> {
668 for module in modules {
669 self.modules.push(crate::core::module_types::ModuleSlot {
670 module: crate::core::module_types::ModuleType::Async(Box::new(module)),
671 failed: false,
672 });
673 }
674 Ok(std::mem::take(self))
675 }
676}