-
Notifications
You must be signed in to change notification settings - Fork 0
Service Integration Guide
Status: ✅ Complete
Last Updated: December 15, 2025
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.
- Adding a New Service
- Service Dependencies
- Feature Gates
- Health Checks
- Testing Integration
- Migration Guide
- Examples
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(())
}
}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(())
}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(())
}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)
}
}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)
}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)))
})?;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)))
})?;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)
})?;-
Update Cargo.toml features in
ricecoder-di/Cargo.toml:
[features]
my_feature = ["dep:ricecoder-my-crate"]- 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(())
}- Update infrastructure registration:
#[cfg(feature = "my_feature")]
register_my_feature_services(container)?;- Add builder method:
#[cfg(feature = "my_feature")]
fn register_my_feature_services(self) -> DIResult<Self> {
register_my_feature_services(&self.container)?;
Ok(self)
}-
Update usage documentation in
src/usage.rs.
Update the feature matrix in documentation:
//! - `my_feature` - My new feature services
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()))
}
}
}container.register_with_health_check(
|_| Ok(Arc::new(MyService::new())),
|service| async move {
service.health_check().await
}
)?;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());
}
}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));
}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);
});
}If you're migrating an existing service to use DI:
-
Wrap existing service in an
Arc:
// Before
pub struct MyService { /* fields */ }
// After
pub type MyService = Arc<InnerMyService>;
struct InnerMyService { /* fields */ }- 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 })
}
}- Update all usage sites to resolve from container instead of direct construction.
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 })
}
}// 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)
}
}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
}
)
}// 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)
}- DI Container Integration Guide - Using the DI container
- Architecture Overview - System architecture
- API Reference - Complete API documentation
Last updated: December 15, 2025