elif_core/container/
autowiring_example.rs

1// Example demonstrating IoC Phase 2: Constructor Injection & Auto-wiring
2
3use crate::container::autowiring::{DependencyResolver, Injectable};
4use crate::container::binding::ServiceBinder;
5use crate::container::descriptor::ServiceId;
6use crate::container::ioc_container::IocContainer;
7use crate::errors::CoreError;
8use std::sync::Arc;
9
10/// Example: E-commerce Order Processing Service
11///
12/// This demonstrates how the new autowiring system works with:
13/// - Constructor injection
14/// - Automatic dependency resolution  
15/// - Optional dependencies
16/// - Complex service graphs
17
18// Domain services
19#[derive(Default)]
20pub struct SqliteUserRepository {
21    connection_string: String,
22}
23
24impl SqliteUserRepository {
25    pub fn find_user(&self, id: u32) -> Option<User> {
26        println!(
27            "Finding user {} from SQLite at {}",
28            id, self.connection_string
29        );
30        Some(User {
31            id,
32            name: format!("User {}", id),
33            email: format!("user{}@example.com", id),
34        })
35    }
36}
37
38#[derive(Default)]
39pub struct PostgresProductRepository;
40
41impl PostgresProductRepository {
42    pub fn find_product(&self, id: u32) -> Option<Product> {
43        println!("Finding product {} from Postgres", id);
44        Some(Product {
45            id,
46            name: format!("Product {}", id),
47            price: 29.99,
48        })
49    }
50}
51
52#[derive(Default)]
53pub struct SmtpEmailService {
54    smtp_server: String,
55}
56
57impl SmtpEmailService {
58    pub fn send_email(&self, to: &str, subject: &str, body: &str) -> Result<(), String> {
59        println!(
60            "Sending email via {} to {}: {} - {}",
61            self.smtp_server, to, subject, body
62        );
63        Ok(())
64    }
65}
66
67#[derive(Default)]
68pub struct PaymentProcessor;
69
70impl PaymentProcessor {
71    pub fn process_payment(&self, amount: f64) -> Result<String, String> {
72        println!("Processing payment of ${:.2}", amount);
73        Ok(format!("payment_id_{}", (amount * 100.0) as u32))
74    }
75}
76
77// Optional services
78#[derive(Default)]
79pub struct MetricsCollector;
80
81impl MetricsCollector {
82    pub fn record_metric(&self, name: &str, value: f64) {
83        println!("Recording metric: {} = {}", name, value);
84    }
85}
86
87// Domain models
88#[derive(Debug)]
89pub struct User {
90    pub id: u32,
91    pub name: String,
92    pub email: String,
93}
94
95#[derive(Debug)]
96pub struct Product {
97    pub id: u32,
98    pub name: String,
99    pub price: f64,
100}
101
102#[derive(Debug)]
103pub struct Order {
104    pub user: User,
105    pub product: Product,
106    pub payment_id: String,
107}
108
109// Main business service with complex dependencies
110pub struct OrderService {
111    user_repo: Arc<SqliteUserRepository>,
112    product_repo: Arc<PostgresProductRepository>,
113    email_service: Arc<SmtpEmailService>,
114    payment_processor: Arc<PaymentProcessor>,
115    metrics: Option<Arc<MetricsCollector>>, // Optional dependency
116}
117
118impl OrderService {
119    pub fn new(
120        user_repo: Arc<SqliteUserRepository>,
121        product_repo: Arc<PostgresProductRepository>,
122        email_service: Arc<SmtpEmailService>,
123        payment_processor: Arc<PaymentProcessor>,
124        metrics: Option<Arc<MetricsCollector>>,
125    ) -> Self {
126        Self {
127            user_repo,
128            product_repo,
129            email_service,
130            payment_processor,
131            metrics,
132        }
133    }
134
135    pub fn create_order(&self, user_id: u32, product_id: u32) -> Result<Order, String> {
136        // Record metrics if available
137        if let Some(metrics) = &self.metrics {
138            metrics.record_metric("order.created", 1.0);
139        }
140
141        // Resolve dependencies and create order
142        let user = self.user_repo.find_user(user_id).ok_or("User not found")?;
143
144        let product = self
145            .product_repo
146            .find_product(product_id)
147            .ok_or("Product not found")?;
148
149        // Process payment
150        let payment_id = self.payment_processor.process_payment(product.price)?;
151
152        // Send confirmation email
153        self.email_service.send_email(
154            &user.email,
155            "Order Confirmation",
156            &format!("Your order for {} has been confirmed!", product.name),
157        )?;
158
159        Ok(Order {
160            user,
161            product,
162            payment_id,
163        })
164    }
165}
166
167// Injectable implementation for OrderService
168impl Injectable for OrderService {
169    fn dependencies() -> Vec<ServiceId> {
170        vec![
171            ServiceId::of::<SqliteUserRepository>(),
172            ServiceId::of::<PostgresProductRepository>(),
173            ServiceId::of::<SmtpEmailService>(),
174            ServiceId::of::<PaymentProcessor>(),
175            ServiceId::of::<MetricsCollector>(), // Optional dependency
176        ]
177    }
178
179    fn create<R: DependencyResolver>(resolver: &R) -> Result<Self, CoreError> {
180        let user_repo = resolver.resolve::<SqliteUserRepository>()?;
181        let product_repo = resolver.resolve::<PostgresProductRepository>()?;
182        let email_service = resolver.resolve::<SmtpEmailService>()?;
183        let payment_processor = resolver.resolve::<PaymentProcessor>()?;
184        let metrics = resolver.try_resolve::<MetricsCollector>(); // Optional
185
186        Ok(OrderService::new(
187            user_repo,
188            product_repo,
189            email_service,
190            payment_processor,
191            metrics,
192        ))
193    }
194}
195
196/// Example usage demonstrating the autowiring system
197pub fn run_autowiring_example() -> Result<(), CoreError> {
198    println!("šŸš€ IoC Phase 2: Constructor Injection & Auto-wiring Example");
199    println!("============================================================");
200
201    // Create and configure the IoC container
202    let mut container = IocContainer::new();
203
204    // Register all services
205    container
206        .bind::<SqliteUserRepository, SqliteUserRepository>()
207        .bind::<PostgresProductRepository, PostgresProductRepository>()
208        .bind::<SmtpEmailService, SmtpEmailService>()
209        .bind::<PaymentProcessor, PaymentProcessor>()
210        .bind::<MetricsCollector, MetricsCollector>() // Optional
211        .bind_injectable::<OrderService>(); // Auto-wiring enabled
212
213    // Build the container (validates dependencies)
214    container.build()?;
215
216    println!(
217        "\nšŸ“¦ Container built successfully with {} services",
218        container.service_count()
219    );
220
221    // Resolve the order service - all dependencies automatically injected!
222    let order_service = container.resolve_injectable::<OrderService>()?;
223
224    println!("\nšŸŽÆ OrderService resolved with auto-wiring!");
225    println!("   Dependencies automatically injected:");
226    println!("   - SqliteUserRepository");
227    println!("   - PostgresProductRepository");
228    println!("   - SmtpEmailService");
229    println!("   - PaymentProcessor");
230    println!("   - MetricsCollector (optional)");
231
232    // Use the service
233    println!("\nšŸ“‹ Creating order...");
234    let order = order_service
235        .create_order(1, 101)
236        .map_err(|e| CoreError::InvalidServiceDescriptor { message: e })?;
237
238    println!("\nāœ… Order created successfully!");
239    println!("   User: {} ({})", order.user.name, order.user.email);
240    println!(
241        "   Product: {} - ${:.2}",
242        order.product.name, order.product.price
243    );
244    println!("   Payment ID: {}", order.payment_id);
245
246    // Demonstrate optional dependency handling
247    println!("\nšŸ”§ Testing without optional dependency...");
248    let mut container_minimal = IocContainer::new();
249    container_minimal
250        .bind::<SqliteUserRepository, SqliteUserRepository>()
251        .bind::<PostgresProductRepository, PostgresProductRepository>()
252        .bind::<SmtpEmailService, SmtpEmailService>()
253        .bind::<PaymentProcessor, PaymentProcessor>();
254    // Note: No MetricsCollector registered
255    // Note: Not using bind_injectable for now due to dependency resolution issues
256
257    container_minimal.build()?;
258
259    // Manually create OrderService to test optional dependency
260    let user_repo = container_minimal.resolve::<SqliteUserRepository>()?;
261    let product_repo = container_minimal.resolve::<PostgresProductRepository>()?;
262    let email_service = container_minimal.resolve::<SmtpEmailService>()?;
263    let payment_processor = container_minimal.resolve::<PaymentProcessor>()?;
264    let metrics = container_minimal.try_resolve::<MetricsCollector>();
265
266    let order_service_minimal = OrderService::new(
267        user_repo,
268        product_repo,
269        email_service,
270        payment_processor,
271        metrics,
272    );
273    println!("āœ… OrderService created even without optional MetricsCollector!");
274
275    let _order2 = order_service_minimal
276        .create_order(2, 102)
277        .map_err(|e| CoreError::InvalidServiceDescriptor { message: e })?;
278    println!("āœ… Order processing works without metrics service");
279
280    Ok(())
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    #[test]
288    fn test_autowiring_example() {
289        run_autowiring_example().expect("Autowiring example should work");
290    }
291
292    #[test]
293    fn test_dependency_resolution() {
294        let deps = OrderService::dependencies();
295        assert_eq!(deps.len(), 5);
296        assert!(deps.contains(&ServiceId::of::<SqliteUserRepository>()));
297        assert!(deps.contains(&ServiceId::of::<PostgresProductRepository>()));
298        assert!(deps.contains(&ServiceId::of::<SmtpEmailService>()));
299        assert!(deps.contains(&ServiceId::of::<PaymentProcessor>()));
300        assert!(deps.contains(&ServiceId::of::<MetricsCollector>()));
301    }
302
303    #[test]
304    fn test_optional_dependency_handling() {
305        let mut container = IocContainer::new();
306
307        // Register only required dependencies
308        container
309            .bind::<SqliteUserRepository, SqliteUserRepository>()
310            .bind::<PostgresProductRepository, PostgresProductRepository>()
311            .bind::<SmtpEmailService, SmtpEmailService>()
312            .bind::<PaymentProcessor, PaymentProcessor>();
313
314        container.build().unwrap();
315
316        // Manually resolve dependencies and create service to test optional handling
317        let user_repo = container.resolve::<SqliteUserRepository>().unwrap();
318        let product_repo = container.resolve::<PostgresProductRepository>().unwrap();
319        let email_service = container.resolve::<SmtpEmailService>().unwrap();
320        let payment_processor = container.resolve::<PaymentProcessor>().unwrap();
321        let metrics = container.try_resolve::<MetricsCollector>(); // Should be None
322
323        assert!(
324            metrics.is_none(),
325            "MetricsCollector should not be available"
326        );
327
328        let order_service = OrderService::new(
329            user_repo,
330            product_repo,
331            email_service,
332            payment_processor,
333            metrics,
334        );
335        let order = order_service.create_order(1, 101).unwrap();
336
337        assert_eq!(order.user.id, 1);
338        assert_eq!(order.product.id, 101);
339        assert!(!order.payment_id.is_empty());
340    }
341
342    #[test]
343    fn test_complex_dependency_graph() {
344        let mut container = IocContainer::new();
345
346        // Register all dependencies including optional ones
347        container
348            .bind::<SqliteUserRepository, SqliteUserRepository>()
349            .bind::<PostgresProductRepository, PostgresProductRepository>()
350            .bind::<SmtpEmailService, SmtpEmailService>()
351            .bind::<PaymentProcessor, PaymentProcessor>()
352            .bind::<MetricsCollector, MetricsCollector>()
353            .bind_injectable::<OrderService>();
354
355        container.build().unwrap();
356
357        // Validate that all services can be resolved
358        assert!(container.resolve::<SqliteUserRepository>().is_ok());
359        assert!(container.resolve::<PostgresProductRepository>().is_ok());
360        assert!(container.resolve::<SmtpEmailService>().is_ok());
361        assert!(container.resolve::<PaymentProcessor>().is_ok());
362        assert!(container.resolve::<MetricsCollector>().is_ok());
363        assert!(container.resolve_injectable::<OrderService>().is_ok());
364    }
365}