Skip to content

Service Integration Guide

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

Service Integration Guide

Status: ✅ Complete

Last Updated: December 15, 2025


Overview

This guide is for crate maintainers who need to add new services to the RiceCoder DI container. It covers the process of registering services, handling dependencies, and ensuring proper integration with the existing service ecosystem.

Table of Contents


Adding a New Service

Step 1: Define the Service

Create your service struct that implements Send + Sync:

use std::sync::Arc;

#[derive(Clone)]
pub struct MyNewService {
    config: Arc<Config>,
    storage: Arc<dyn Storage>,
}

impl MyNewService {
    pub fn new(config: Arc<Config>, storage: Arc<dyn Storage>) -> Self {
        Self { config, storage }
    }

    pub fn do_something(&self) -> Result<(), Error> {
        // Service implementation
        Ok(())
    }
}

Step 2: Add Service Registration Function

Add a registration function to ricecoder-di/src/services.rs:

/// Register MyNewService
pub fn register_my_new_service(container: &DIContainer) -> DIResult<()> {
    container.register(|container| {
        let config = container.resolve::<Config>()?;
        let storage = container.resolve::<dyn Storage>()?;
        Ok(Arc::new(MyNewService::new(config, storage)))
    })?;
    Ok(())
}

Step 3: Add to Infrastructure Services

Update the register_infrastructure_services function:

pub fn register_infrastructure_services(container: &DIContainer) -> DIResult<()> {
    // ... existing registrations ...

    // Add your new service
    register_my_new_service(container)?;

    Ok(())
}

Step 4: Add Builder Method (Optional)

Add a convenience method to DIContainerBuilderExt:

impl DIContainerBuilderExt for DIContainerBuilder {
    // ... existing methods ...

    /// Register MyNewService
    fn register_my_new_service(self) -> DIResult<Self> {
        register_my_new_service(&self.container)?;
        Ok(self)
    }
}

Step 5: Update Feature Gates (if needed)

If your service should be feature-gated, add it to the appropriate feature section:

#[cfg(feature = "my_feature")]
pub fn register_my_new_service(container: &DIContainer) -> DIResult<()> {
    // Implementation
}

And update the builder extension:

#[cfg(feature = "my_feature")]
fn register_my_new_service(self) -> DIResult<Self> {
    register_my_new_service(&self.container)?;
    Ok(self)
}

Service Dependencies

Resolving Dependencies

Services can depend on other services through the container:

container.register(|container| {
    let config = container.resolve::<ConfigManager>()?;
    let storage = container.resolve::<StorageManager>()?;
    let logger = container.resolve::<ActivityLogger>()?;

    Ok(Arc::new(MyService::new(config, storage, logger)))
})?;

Interface Dependencies

Use trait objects for flexible dependencies:

pub trait Storage: Send + Sync {
    async fn save(&self, key: &str, data: &[u8]) -> Result<()>;
    async fn load(&self, key: &str) -> Result<Vec<u8>>;
}

container.register(|container| {
    let storage: Arc<dyn Storage> = container.resolve()?;
    Ok(Arc::new(MyService::new(storage)))
})?;

Optional Dependencies

Handle optional dependencies gracefully:

container.register(|container| {
    let base_service = Arc::new(BaseService::new());

    // Add optional features if available
    #[cfg(feature = "advanced")]
    let base_service = {
        let advanced_feature = container.resolve::<AdvancedFeature>()?;
        base_service.with_advanced(advanced_feature)
    };

    Ok(base_service)
})?;

Feature Gates

Adding a New Feature

  1. Update Cargo.toml features in ricecoder-di/Cargo.toml:
[features]
my_feature = ["dep:ricecoder-my-crate"]
  1. Add feature-gated registration:
#[cfg(feature = "my_feature")]
pub fn register_my_feature_services(container: &DIContainer) -> DIResult<()> {
    container.register(|_| Ok(Arc::new(MyFeatureService::new())))?;
    Ok(())
}
  1. Update infrastructure registration:
#[cfg(feature = "my_feature")]
register_my_feature_services(container)?;
  1. Add builder method:
#[cfg(feature = "my_feature")]
fn register_my_feature_services(self) -> DIResult<Self> {
    register_my_feature_services(&self.container)?;
    Ok(self)
}
  1. Update usage documentation in src/usage.rs.

Feature Matrix

Update the feature matrix in documentation:

//! - `my_feature` - My new feature services

Health Checks

Implementing Health Checks

Make your service implement the HealthCheck trait:

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

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

Registering with Health Checks

container.register_with_health_check(
    |_| Ok(Arc::new(MyService::new())),
    |service| async move {
        service.health_check().await
    }
)?;

Testing Integration

Unit Tests

Test service registration and resolution:

#[cfg(test)]
mod tests {
    use super::*;
    use ricecoder_di::DIContainer;

    #[test]
    fn test_my_service_registration() {
        let container = DIContainer::new();
        register_my_new_service(&container).unwrap();

        let service = container.resolve::<MyNewService>().unwrap();
        assert!(service.do_something().is_ok());
    }
}

Integration Tests

Test with other services:

#[test]
fn test_service_integration() {
    let container = create_application_container().unwrap();

    // Should be able to resolve all services
    let my_service = container.resolve::<MyNewService>().unwrap();
    let config = container.resolve::<ConfigManager>().unwrap();

    // Test interaction
    assert!(my_service.uses_config(&config));
}

Property-Based Tests

Test service properties:

#[test]
fn test_service_properties() {
    proptest!(|(input in ".*")| {
        let container = DIContainer::new();
        register_my_new_service(&container).unwrap();
        let service = container.resolve::<MyNewService>().unwrap();

        // Test deterministic behavior
        let result1 = service.process(&input);
        let result2 = service.process(&input);
        prop_assert_eq!(result1, result2);
    });
}

Migration Guide

Migrating Existing Services

If you're migrating an existing service to use DI:

  1. Wrap existing service in an Arc:
// Before
pub struct MyService { /* fields */ }

// After
pub type MyService = Arc<InnerMyService>;

struct InnerMyService { /* fields */ }
  1. Update constructors to work with DI:
// Before
impl MyService {
    pub fn new(config: Config) -> Self {
        Self { config }
    }
}

// After
impl MyService {
    pub fn new(config: Arc<Config>) -> Self {
        Arc::new(InnerMyService { config })
    }
}
  1. Update all usage sites to resolve from container instead of direct construction.

Backward Compatibility

Maintain backward compatibility during migration:

impl MyService {
    // Keep old constructor for backward compatibility
    pub fn from_config(config: Config) -> Self {
        Self::new(Arc::new(config))
    }

    // New DI-friendly constructor
    pub fn new(config: Arc<Config>) -> Self {
        Arc::new(InnerMyService { config })
    }
}

Examples

Complete Service Integration

// In your crate's lib.rs
pub mod services;
pub mod models;

// In services.rs
use ricecoder_di::{DIContainer, DIResult};
use std::sync::Arc;

pub struct DocumentService {
    storage: Arc<dyn Storage>,
    validator: Arc<DocumentValidator>,
}

impl DocumentService {
    pub fn new(storage: Arc<dyn Storage>, validator: Arc<DocumentValidator>) -> Arc<Self> {
        Arc::new(Self { storage, validator })
    }
}

// Registration function
pub fn register_document_services(container: &DIContainer) -> DIResult<()> {
    // Register dependencies first
    container.register(|_| Ok(Arc::new(DocumentValidator::new())))?;

    // Register main service
    container.register(|container| {
        let storage = container.resolve::<dyn Storage>()?;
        let validator = container.resolve::<DocumentValidator>()?;
        Ok(DocumentService::new(storage, validator))
    })?;

    Ok(())
}

// In ricecoder-di/src/services.rs
#[cfg(feature = "documents")]
pub fn register_document_services(container: &DIContainer) -> DIResult<()> {
    ricecoder_documents::services::register_document_services(container)
}

// Builder extension
#[cfg(feature = "documents")]
impl DIContainerBuilderExt for DIContainerBuilder {
    fn register_document_services(self) -> DIResult<Self> {
        register_document_services(&self.container)?;
        Ok(self)
    }
}

Service with Health Checks

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

pub struct DatabaseService {
    pool: Arc<ConnectionPool>,
}

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

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

Feature-Gated Service

// In Cargo.toml
[features]
ml = ["dep:ricecoder-ml"]

// In services.rs
#[cfg(feature = "ml")]
pub fn register_ml_services(container: &DIContainer) -> DIResult<()> {
    container.register(|_| Ok(Arc::new(MLService::new())))?;
    Ok(())
}

// In ricecoder-di/src/services.rs
#[cfg(feature = "ml")]
register_ml_services(container)?;

// Builder
#[cfg(feature = "ml")]
fn register_ml_services(self) -> DIResult<Self> {
    register_ml_services(&self.container)?;
    Ok(self)
}

See Also


Last updated: December 15, 2025

Clone this wiki locally