flow-di 0.1.0

A dependency injection framework for Rust inspired by C# AutoFac and Microsoft.Extensions.DependencyInjection
Documentation
//! Basic usage example
//!
//! This example demonstrates the basic features of Flow-DI, including:
//! - Service registration and resolution
//! - Different lifetime management
//! - Dependency injection
//! - Named services

use flow_di::{ContainerBuilder, DiResult, ServiceProviderExt};
use std::sync::Arc;

// Define service interface
trait ILogger: Send + Sync {
    fn log(&self, message: &str);
    fn get_logs(&self) -> Vec<String>;
}

trait IRepository: Send + Sync {
    fn get_data(&self, id: i32) -> String;
    fn save_data(&self, id: i32, data: &str) -> bool;
}

trait IEmailService: Send + Sync {
    fn send_email(&self, to: &str, subject: &str, body: &str) -> bool;
}

// Logger service implementation
#[derive(Debug)]
struct ConsoleLogger {
    prefix: String,
    logs: std::sync::Mutex<Vec<String>>,
}

impl ConsoleLogger {
    fn new(prefix: String) -> Self {
        Self {
            prefix,
            logs: std::sync::Mutex::new(Vec::new()),
        }
    }
}

impl ILogger for ConsoleLogger {
    fn log(&self, message: &str) {
        let log_message = format!("[{}] {}", self.prefix, message);
        println!("{log_message}");

        if let Ok(mut logs) = self.logs.lock() {
            logs.push(log_message);
        }
    }

    fn get_logs(&self) -> Vec<String> {
        self.logs.lock().unwrap().clone()
    }
}

// Database repository implementation
struct DatabaseRepository {
    logger: Arc<dyn ILogger>,
    connection_string: Arc<String>,
}

impl DatabaseRepository {
    fn new(logger: Arc<ConsoleLogger>, connection_string: Arc<String>) -> Self {
        let logger: Arc<dyn ILogger> = logger;
        logger.log("DatabaseRepository initialized");
        Self {
            logger,
            connection_string,
        }
    }
}

impl IRepository for DatabaseRepository {
    fn get_data(&self, id: i32) -> String {
        self.logger.log(&format!("Fetching data for ID: {id}"));
        format!("Data {id} from {}", self.connection_string)
    }

    fn save_data(&self, id: i32, data: &str) -> bool {
        self.logger
            .log(&format!("Saving data for ID: {id} -> {data}"));
        true
    }
}

// In-memory repository implementation
struct InMemoryRepository {
    logger: Arc<dyn ILogger>,
    data: std::sync::Mutex<std::collections::HashMap<i32, String>>,
}

impl InMemoryRepository {
    fn new(logger: Arc<ConsoleLogger>) -> Self {
        let logger: Arc<dyn ILogger> = logger;
        logger.log("InMemoryRepository initialized");
        Self {
            logger,
            data: std::sync::Mutex::new(std::collections::HashMap::new()),
        }
    }
}

impl IRepository for InMemoryRepository {
    fn get_data(&self, id: i32) -> String {
        self.logger
            .log(&format!("Fetching data from memory for ID: {id}"));
        let data = self.data.lock().unwrap();
        data.get(&id)
            .cloned()
            .unwrap_or_else(|| format!("No data for ID: {id}"))
    }

    fn save_data(&self, id: i32, data: &str) -> bool {
        self.logger
            .log(&format!("Saving data to memory for ID: {id} -> {data}"));
        let mut storage = self.data.lock().unwrap();
        storage.insert(id, data.to_string());
        true
    }
}

// Email service implementation
struct SmtpEmailService {
    logger: Arc<dyn ILogger>,
    smtp_server: Arc<String>,
}

impl SmtpEmailService {
    fn new(logger: Arc<ConsoleLogger>, smtp_server: Arc<String>) -> Self {
        let logger: Arc<dyn ILogger> = logger;
        logger.log("SmtpEmailService initialized");
        Self {
            logger,
            smtp_server,
        }
    }
}

impl IEmailService for SmtpEmailService {
    fn send_email(&self, to: &str, subject: &str, body: &str) -> bool {
        self.logger.log(&format!(
            "Sending email via {} to: {to} | Subject: {subject}",
            self.smtp_server
        ));
        println!("Email sent to: {to}\nSubject: {subject}\nBody: {body}");
        true
    }
}

// Business service
struct UserService {
    repository: Arc<dyn IRepository>,
    email_service: Arc<dyn IEmailService>,
    logger: Arc<dyn ILogger>,
}

impl UserService {
    fn new_concrete(
        repository: Arc<DatabaseRepository>,
        email_service: Arc<SmtpEmailService>,
        logger: Arc<ConsoleLogger>,
    ) -> Self {
        let repository: Arc<dyn IRepository> = repository;
        let email_service: Arc<dyn IEmailService> = email_service;
        let logger: Arc<dyn ILogger> = logger;
        logger.log("UserService initialized");
        Self {
            repository,
            email_service,
            logger,
        }
    }

    fn new_memory_concrete(
        repository: Arc<InMemoryRepository>,
        email_service: Arc<SmtpEmailService>,
        logger: Arc<ConsoleLogger>,
    ) -> Self {
        let repository: Arc<dyn IRepository> = repository;
        let email_service: Arc<dyn IEmailService> = email_service;
        let logger: Arc<dyn ILogger> = logger;
        logger.log("UserService initialized");
        Self {
            repository,
            email_service,
            logger,
        }
    }

    fn create_user(&self, id: i32, name: &str, email: &str) -> bool {
        self.logger.log(&format!(
            "Creating user: ID={id}, Name={name}, Email={email}"
        ));

        // Save user data
        let user_data = format!("{{\"name\":\"{name}\",\"email\":\"{email}\"}}");
        if self.repository.save_data(id, &user_data) {
            // Send welcome email
            let subject = "Welcome!";
            let body = &format!("Hello {name}, welcome to our service!");
            self.email_service.send_email(email, subject, body);

            self.logger.log("User created successfully");
            true
        } else {
            self.logger.log("Failed to create user");
            false
        }
    }

    fn get_user(&self, id: i32) -> Option<String> {
        self.logger.log(&format!("Getting user with ID: {id}"));
        let data = self.repository.get_data(id);

        if data.starts_with("No data") {
            None
        } else {
            Some(data)
        }
    }
}

fn main() -> DiResult<()> {
    println!("=== Flow-DI Basic Usage Example ===\n");

    // Create container and register services
    let provider = ContainerBuilder::new()
        // Register configuration (singleton instance)
        .add_instance("postgresql://localhost:5432/myapp".to_string())
        .add_named_instance("smtp_server", "smtp.gmail.com:587".to_string())
        // Register logger service (singleton)
        .add_singleton_simple::<ConsoleLogger, ConsoleLogger>(|| {
            ConsoleLogger::new("APP".to_string())
        })
        // Register named logger service
        .add_named_singleton_simple::<ConsoleLogger, ConsoleLogger>("file_logger", || {
            ConsoleLogger::new("FILE".to_string())
        })
        // Register database repository (scoped, with dependency injection)
        .add_scoped::<DatabaseRepository, DatabaseRepository>(|provider| {
            let logger = provider.get_required_service::<ConsoleLogger>()?;
            let connection_string = provider.get_required_service::<String>()?;
            Ok(DatabaseRepository::new(logger, connection_string))
        })
        // Register in-memory repository (named scoped service)
        .add_named_scoped::<InMemoryRepository, InMemoryRepository>("memory", |provider| {
            let logger = provider.get_required_service::<ConsoleLogger>()?;
            Ok(InMemoryRepository::new(logger))
        })
        // Register email service (singleton, with dependency injection)
        .add_singleton::<SmtpEmailService, SmtpEmailService>(|provider| {
            let logger = provider.get_required_service::<ConsoleLogger>()?;
            let smtp_server = provider.get_required_keyed_service::<String>("smtp_server")?;
            Ok(SmtpEmailService::new(logger, smtp_server))
        })
        // Register user service (transient, with multiple dependencies)
        .add_transient::<UserService, UserService>(|provider| {
            let repository = provider.get_required_service::<DatabaseRepository>()?;
            let email_service = provider.get_required_service::<SmtpEmailService>()?;
            let logger = provider.get_required_service::<ConsoleLogger>()?;
            Ok(UserService::new_concrete(repository, email_service, logger))
        })
        // Register named user service (using in-memory repository)
        .add_named_transient::<UserService, UserService>("memory_user_service", |provider| {
            let repository = provider.get_required_keyed_service::<InMemoryRepository>("memory")?;
            let email_service = provider.get_required_service::<SmtpEmailService>()?;
            let logger = provider.get_required_keyed_service::<ConsoleLogger>("file_logger")?;
            Ok(UserService::new_memory_concrete(
                repository,
                email_service,
                logger,
            ))
        })
        .build();

    // Demonstrate basic service resolution
    println!("1. 基础服务解析");
    let logger = provider.get_required_service::<ConsoleLogger>()?;
    logger.log("Application started");

    // Demonstrate singleton behavior
    println!("\n2. 单例行为演示");
    let logger1 = provider.get_required_service::<ConsoleLogger>()?;
    let logger2 = provider.get_required_service::<ConsoleLogger>()?;
    println!(
        "Logger instances are same: {}",
        Arc::ptr_eq(&logger1, &logger2)
    );

    // Demonstrate scoped service
    println!("\n3. 作用域服务演示");
    {
        let mut scope1 = provider.create_scope()?;
        let repo1_a = scope1.get_required_service::<DatabaseRepository>()?;
        let repo1_b = scope1.get_required_service::<DatabaseRepository>()?;
        println!(
            "Scope1 - Repository instances are same: {}",
            Arc::ptr_eq(&repo1_a, &repo1_b)
        );

        let mut scope2 = provider.create_scope()?;
        let repo2 = scope2.get_required_service::<DatabaseRepository>()?;
        println!(
            "Different scopes - Repository instances are same: {}",
            Arc::ptr_eq(&repo1_a, &repo2)
        );

        scope1.dispose();
        scope2.dispose();
    }

    // Demonstrate transient service
    println!("\n4. 瞬时服务演示");
    let mut scope = provider.create_scope()?;
    let user_service1 = scope.get_required_service::<UserService>()?;
    let user_service2 = scope.get_required_service::<UserService>()?;
    println!(
        "Transient services are same: {}",
        Arc::ptr_eq(&user_service1, &user_service2)
    );

    // Use user service
    println!("\n5. 业务逻辑演示");
    user_service1.create_user(1, "Alice", "alice@example.com");
    user_service1.create_user(2, "Bob", "bob@example.com");

    if let Some(user_data) = user_service1.get_user(1) {
        println!("Retrieved user: {user_data}");
    }

    // Demonstrate named service
    println!("\n6. 命名服务演示");
    let memory_user_service =
        scope.get_required_keyed_service::<UserService>("memory_user_service")?;
    memory_user_service.create_user(10, "Charlie", "charlie@example.com");

    if let Some(user_data) = memory_user_service.get_user(10) {
        println!("Retrieved user from memory: {user_data}");
    } else {
        println!("User not found in memory repository");
    }

    // Demonstrate service collections
    println!("\n7. 服务集合演示");
    let all_repositories = scope.get_services::<DatabaseRepository>()?;
    println!(
        "Total repository implementations: {}",
        all_repositories.len()
    );

    // Display logs
    println!("\n8. 日志记录");
    let main_logger = scope.get_required_service::<ConsoleLogger>()?;
    let logs = main_logger.get_logs();
    println!("Total log entries: {}", logs.len());
    for (i, log) in logs.iter().take(5).enumerate() {
        println!("  {}. {log}", i + 1);
    }
    if logs.len() > 5 {
        println!("  ... and {} more", logs.len() - 5);
    }

    scope.dispose();
    println!("\n=== Example Completed ===");
    Ok(())
}