Skip to content

DI Container Integration Guide

Mo Abualruz edited this page Dec 16, 2025 · 2 revisions

DI Container Integration Guide

Status: ✅ Complete

Last Updated: December 15, 2025


Overview

This guide explains how to use the RiceCoder Dependency Injection (DI) container (ricecoder-di) for service registration, resolution, and management. The DI container provides a centralized way to wire services across all RiceCoder crates.

Table of Contents


Quick Start

use ricecoder_di::create_application_container;

// Create container with core services
let container = create_application_container().unwrap();

// Resolve services
let session_manager = container.resolve::<ricecoder_sessions::SessionManager>().unwrap();
let provider_manager = container.resolve::<ricecoder_providers::provider::manager::ProviderManager>().unwrap();

Service Registration

Basic Registration

Register a service with a factory function:

use ricecoder_di::{DIContainer, register_service};

let container = DIContainer::new();

// Register with closure
container.register(|_| {
    let service = Arc::new(MyService::new());
    Ok(service)
}).unwrap();

// Or use the macro
register_service!(container, MyService, |_| Ok(Arc::new(MyService::new())));

Registration with Dependencies

Services can depend on other services:

container.register(|container| {
    let dependency = container.resolve::<DependencyService>()?;
    let service = Arc::new(MyService::new(dependency));
    Ok(service)
}).unwrap();

Service Lifetimes

Singleton Services

Created once and reused throughout the application:

container.register(|_| {
    Ok(Arc::new(SingletonService::new()))
}).unwrap();

Transient Services

Created each time they're requested:

container.register_transient(|_| {
    Ok(Arc::new(TransientService::new()))
}).unwrap();

Scoped Services

Created once per scope and reused within that scope:

use ricecoder_di::ServiceScope;

container.register_scoped(|_| {
    Ok(Arc::new(ScopedService::new()))
}).unwrap();

let scope = ServiceScope::new();
let service1 = container.resolve_with_scope::<ScopedService>(Some(&scope)).unwrap();
let service2 = container.resolve_with_scope::<ScopedService>(Some(&scope)).unwrap();
// service1 and service2 are the same instance

Service Resolution

Basic Resolution

let service = container.resolve::<MyService>().unwrap();

Resolution with Scope

let scope = ServiceScope::new();
let service = container.resolve_with_scope::<MyService>(Some(&scope)).unwrap();

Checking Service Availability

if container.is_registered::<MyService>() {
    let service = container.resolve::<MyService>().unwrap();
    // Use service
}

Feature-Gated Services

Services can be conditionally compiled based on Cargo features:

[dependencies]
ricecoder-di = { version = "0.1", features = ["storage", "research"] }

Available Features

Feature Services Included
storage StorageManager, FileStorage, MemoryStorage
research ResearchManager, CodebaseScanner, SemanticIndexer
workflows WorkflowEngine, WorkflowManager
execution ExecutionEngine, CommandExecutor
mcp MCPClient, MCPServer
tools ToolRegistry, ToolExecutor
config ConfigManager, ConfigLoader
activity-log ActivityLogger, AuditLogger, SessionTracker
orchestration WorkspaceOrchestrator, OperationManager
specs SpecManager, SpecValidator, SpecCache
undo-redo UndoManager, RedoManager, HistoryManager
vcs VCSManager, GitIntegration, RepositoryManager
permissions PermissionManager, PermissionChecker, AuditLogger
security AccessControl, EncryptionService, ValidationService
cache CacheManager, CacheStorage, CacheStrategy
domain DomainService, Repository, EntityManager
learning LearningManager, PatternCapturer, RuleValidator
industry AuthService, ComplianceManager, ConnectionManager
safety SafetyMonitor, RiskAssessor, ConstraintValidator
files FileManager, FileWatcher, TransactionManager
themes ThemeManager, ThemeLoader, ThemeRegistry
images ImageHandler, ImageAnalyzer, ImageCache
completion GenericCompletionEngine
lsp LSP services
modes ModeManager
commands CommandManager, CommandRegistry
hooks HookRegistry
keybinds KeybindManager
teams TeamManager
refactoring ConfigManager, ProviderRegistry
full All features enabled

Using Feature-Gated Services

#[cfg(feature = "storage")]
{
    let storage_manager = container.resolve::<ricecoder_storage::StorageManager>().unwrap();
    // Use storage services
}

#[cfg(feature = "research")]
{
    let research_manager = container.resolve::<ricecoder_research::ResearchManager>().unwrap();
    // Use research services
}

Conditional Registration

Register services based on runtime conditions:

// Register based on configuration
container.register(|_| {
    if config.use_file_storage {
        Ok(Arc::new(FileStorage::new(&config.storage_path)))
    } else {
        Ok(Arc::new(MemoryStorage::new()))
    }
}).unwrap();

// Register based on environment
container.register(|_| {
    let provider = if std::env::var("USE_OPENAI").is_ok() {
        Arc::new(OpenAIProvider::new())
    } else {
        Arc::new(AnthropicProvider::new())
    };
    Ok(provider)
}).unwrap();

Health Checks

Services can implement health monitoring:

use ricecoder_di::{HealthCheck, HealthStatus};
use async_trait::async_trait;

#[async_trait]
impl HealthCheck for MyService {
    async fn health_check(&self) -> DIResult<HealthStatus> {
        if self.is_connected() {
            Ok(HealthStatus::Healthy)
        } else {
            Ok(HealthStatus::Unhealthy("Connection lost".to_string()))
        }
    }
}

// Register with health check
container.register_with_health_check(
    |_| Ok(Arc::new(MyService::new())),
    |service| async move {
        service.health_check().await
    }
).unwrap();

// Check all services
let health_results = container.health_check_all().unwrap();
for (service_name, status) in health_results {
    match status {
        HealthStatus::Healthy => println!("✅ {} is healthy", service_name),
        HealthStatus::Degraded(reason) => println!("⚠️  {} is degraded: {}", service_name, reason),
        HealthStatus::Unhealthy(reason) => println!("❌ {} is unhealthy: {}", service_name, reason),
    }
}

Error Handling

Handle common DI errors:

use ricecoder_di::{DIError, DIResult};

match container.resolve::<MyService>() {
    Ok(service) => {
        // Use service
    }
    Err(DIError::ServiceNotRegistered { service_type }) => {
        eprintln!("Service not registered: {}", service_type);
        // Handle missing service
    }
    Err(DIError::DependencyResolutionFailed { message }) => {
        eprintln!("Dependency resolution failed: {}", message);
        // Handle dependency issues
    }
    Err(DIError::CircularDependency { service_chain }) => {
        eprintln!("Circular dependency detected: {}", service_chain);
        // Handle circular dependencies
    }
    Err(e) => {
        eprintln!("DI error: {}", e);
        // Handle other errors
    }
}

Best Practices

1. Use Appropriate Lifetimes

  • Singleton: Shared resources, expensive objects, stateless services
  • Transient: Lightweight services, per-operation state
  • Scoped: Request-scoped data, user-specific services

2. Thread Safety

All services must be Send + Sync:

struct MyService {
    data: Arc<RwLock<Data>>, // Use Arc<RwLock<>> for shared mutable state
}

3. Factory Functions

Use factory functions for complex initialization:

container.register(|container| {
    let config = container.resolve::<ConfigManager>()?;
    let storage = container.resolve::<StorageManager>()?;
    Ok(Arc::new(MyService::with_dependencies(config, storage)))
}).unwrap();

4. Feature Flags

Use feature flags for optional functionality:

#[cfg(feature = "advanced_features")]
container.register(|_| Ok(Arc::new(AdvancedService::new()))).unwrap();

5. Testing

Test services in isolation:

#[test]
fn test_service_registration() {
    let container = DIContainer::new();
    container.register(|_| Ok(Arc::new(TestService::new()))).unwrap();

    let service = container.resolve::<TestService>().unwrap();
    assert!(service.is_ready());
}

Examples

Basic Application Setup

use ricecoder_di::{DIContainerBuilder, DIContainerBuilderExt};

fn setup_container() -> DIResult<DIContainer> {
    let container = DIContainerBuilder::new()
        .register_infrastructure_services()?
        .register_use_cases()?
        .build()?;

    Ok(container)
}

Advanced Setup with Features

fn setup_full_container() -> DIResult<DIContainer> {
    let mut builder = DIContainerBuilder::new()
        .register_infrastructure_services()?
        .register_use_cases()?;

    // Add optional services based on features
    #[cfg(feature = "storage")]
    { builder = builder.register_storage_services()?; }

    #[cfg(feature = "research")]
    { builder = builder.register_research_services()?; }

    #[cfg(feature = "workflows")]
    { builder = builder.register_workflow_services()?; }

    let container = builder.build()?;
    Ok(container)
}

Service with Health Checks

struct DatabaseService {
    connection_pool: Arc<ConnectionPool>,
}

#[async_trait]
impl HealthCheck for DatabaseService {
    async fn health_check(&self) -> DIResult<HealthStatus> {
        match self.connection_pool.health_check().await {
            Ok(_) => Ok(HealthStatus::Healthy),
            Err(e) => Ok(HealthStatus::Unhealthy(e.to_string())),
        }
    }
}

fn register_database_service(container: &DIContainer) -> DIResult<()> {
    container.register_with_health_check(
        |_| Ok(Arc::new(DatabaseService::new())),
        |service| async move {
            service.health_check().await
        }
    )
}

See Also


Last updated: December 15, 2025

Clone this wiki locally