title: "Generic Service Implementations Roadmap" description: "Transforming hardcoded core service implementations into generic interfaces with pluggable providers" category: roadmap tags:

  • architecture
  • refactoring
  • dependency-injection
  • services
  • generic-programming last_updated: March 27, 2025 version: 1.0

Generic Service Implementations Roadmap

Overview

This roadmap outlines the steps to transform hardcoded service implementations in the core module into generic interfaces with pluggable providers. Following the successful pattern of refactoring our auth system from Entra-specific to a generic OAuth implementation, we'll apply the same approach to other core services that are currently hardcoded.

Current Progress

  • Phase 1 (Database Service Generalization): 100% Complete
  • Phase 2 (Health Service Generalization): 100% Complete
  • Phase 3 (Cache Service Generalization): 100% Complete
  • Phase 4 (Collection Model Generalization): 100% Complete
  • Phase 5 (Logging Service Generalization): 100% Complete
  • Overall Progress: 71% (5/7 phases completed)

Current Status

We've identified several hardcoded implementations in the core that should be made generic:

  1. Database Service: Currently hardcoded to use InMemoryDatabase ✅
  2. Health Service: Hardcoded health indicators ✅
  3. Cache Implementation: Specifically tied to Moka cache ✅
  4. Database Collection Model: Specific methods for user collection ✅
  5. Logging Service: Direct use of tracing crate ✅
  6. Database Provider: Only supports in-memory database

Target State

Services in the core module should:

  1. Be defined through generic traits/interfaces
  2. Support multiple implementations through providers
  3. Use dependency injection for wiring
  4. Allow configuration-based selection of providers
  5. Support testing through mock implementations

Implementation Progress Tracking

Phase 1: Database Service Generalization

  1. Define Database Interface

    • Create DatabaseInterface trait to abstract database operations
    • Define key operations (get, set, delete, query)
    • Add generic type parameters for flexible implementation
    • Create provider trait for database instantiation

    Updated at: March 26, 2025 - Completed implementation of DatabaseOperations and DatabaseProvider traits

  2. Refactor In-Memory Database

    • Make InMemoryDatabase implement the new interface
    • Update database service to use the interface
    • Create separate implementation module
    • Add tests for the implementation

    Updated at: March 26, 2025 - Implemented InMemoryDatabase that uses the new interface

  3. Implement Configuration System

    • Update DatabaseConfig to support provider selection
    • Implement provider registry
    • Create factory method for database instantiation
    • Add configuration validation

    Updated at: March 26, 2025 - Created DatabaseProviderRegistry with provider selection and validation

Phase 2: Health Service Generalization

  1. Define Health Check Interface

    • Create HealthIndicator trait (already exists but needs enhancement)
    • Add provider system for health indicators
    • Create registration mechanism for custom indicators
    • Implement discovery mechanism for auto-registration

    Updated at: March 26, 2025 - Completed all Health Indicator Interface tasks, including dynamic discovery support with the HealthDiscoveryService

  2. Refactor Health Indicators

    • Move existing indicators to separate modules
    • Make all indicators pluggable
    • Implement conditional indicators based on config
    • Add dynamic health indicator support

    Updated at: March 26, 2025 - Completed all Health Indicator refactoring, including dynamic indicator registration and discovery

  3. Implement Health Dashboard

    • Centralize health data collection
    • Add metadata support for indicators
    • Implement status aggregation
    • Create detailed reporting system

    Updated at: March 26, 2025 - Implemented complete Health Dashboard with detailed reporting, history tracking, and dynamic indicator support

Phase 3: Cache Service Generalization

  1. Define Cache Interface

    • Create CacheProvider trait
    • Abstract cache operations from implementation
    • Support different serialization strategies
    • Define eviction policy interface

    Updated at: March 26, 2025 - Created comprehensive cache provider interface with support for various eviction policies and serialization strategies

  2. Refactor Moka Cache Implementation

    • Make the existing implementation a provider
    • Create separate module for Moka implementation
    • Remove direct Moka dependencies from core
    • Implement adapter pattern for Moka

    Updated at: March 26, 2025 - Replaced direct Moka dependency with a custom implementation that follows the new generic interface

  3. Add Alternative Cache Implementation

    • Implement simple in-memory cache
    • Create Redis cache provider (placeholder)
    • Add configuration for selecting providers
    • Implement cache provider factory

    Updated at: March 26, 2025 - Implemented in-memory cache provider and Redis placeholder with provider factory for cache instantiation

  4. Implement Two-Tier Cache Fallback

    • Create TwoTierCache implementation
    • Support fast cache (memory) with slow cache (Redis) fallback
    • Add automatic promotion of items from slow to fast cache
    • Support configurable TTLs for each cache level

    Updated at: March 26, 2025 - Implemented TwoTierCache with fast/slow cache layers, automatic promotion, and configurable TTLs

Phase 4: Collection Model Generalization

  1. Define Entity Interface

    • Create generic entity trait
    • Define common CRUD operations
    • Implement repository pattern
    • Support type-safe collections

    Updated at: March 26, 2025 - Completed implementation of Entity and Repository traits, with generic ID type support

  2. Refactor User Collection

    • Abstract user-specific methods to generic pattern
    • Create repository implementations
    • Implement type mapping between layers
    • Add comprehensive tests

    Updated at: March 26, 2025 - Implemented InMemoryRepository and UserService that follows the repository pattern

  3. Create Repository Pattern Documentation

    • Document repository pattern implementation
    • Add examples for custom repositories
    • Create migration guide for existing code
    • Update architecture documentation

    Updated at: March 26, 2025 - Created comprehensive documentation for the repository pattern in docs/examples/repository-pattern-example.md

Phase 5: Logging Service Generalization

  1. Define Logging Interface

    • Create LoggingProvider trait
    • Abstract core logging operations
    • Support structured logging
    • Define log filtering and sampling interfaces

    Updated at: March 26, 2025 - Implemented LoggingProvider trait with comprehensive interface for all logging operations

  2. Refactor Existing Logging Implementation

    • Make current logging system a provider
    • Create separate module for implementation
    • Remove direct logging dependencies from core
    • Add adapter pattern for current logger

    Updated at: March 26, 2025 - Created TracingLoggerProvider to adapt the existing tracing-based logging to the new interface

  3. Add Enterprise Logging Providers

    • Create console logging provider
    • Add support for structured logging format
    • Implement provider registry for swappable implementations
    • Support global context and child loggers

    Updated at: March 26, 2025 - Implemented ConsoleLoggerProvider with colored output and created LoggingProviderRegistry for dynamic selection

Phase 6: Observability Service Generalization

  1. Define Observability Interface

    • Create ObservabilityProvider trait
    • Define metrics, tracing, and profiling operations
    • Support context propagation
    • Create sampling and filtering mechanisms

    Updated at: Not started

  2. Refactor Metrics Implementation

    • Adapt current metrics to provider interface
    • Create separate metrics module
    • Implement adapter for current metrics
    • Add telemetry correlation support

    Updated at: Not started

  3. Add Enterprise Observability Providers

    • Create Dynatrace integration
    • Add OpenTelemetry support
    • Implement Prometheus metrics provider
    • Support distributed tracing with Jaeger

    Updated at: Not started

Phase 7: Configuration Service Generalization

  1. Define Configuration Interface

    • Create ConfigProvider trait
    • Abstract configuration loading and refreshing
    • Support environment-specific configuration
    • Add configuration change notifications

    Updated at: Not started

  2. Refactor Static Configuration

    • Make current file-based config a provider
    • Create separate config modules by domain
    • Support hot reloading of configuration
    • Add validation rules framework

    Updated at: Not started

  3. Add Dynamic Configuration Providers

    • Implement environment variable provider
    • Create AWS Parameter Store/Secrets Manager provider
    • Add etcd/Consul KV integration
    • Support feature flags and A/B testing config

    Updated at: Not started

Implementation Status

  • Overall Progress: 71% complete (Phases 1-5 fully completed)
  • Last Updated: March 26, 2025
  • Next Milestone: Begin Observability Service Generalization (Phase 6)
  • Current Focus: Completed implementation of logging service generalization with provider registry and multiple implementations

Success Criteria

  1. No hardcoded service implementations in the core module
  2. All services defined by interfaces with at least two implementations
  3. 100% test coverage of interfaces and 90%+ for implementations
  4. Comprehensive documentation for extending services
  5. Migration guide for updating client code
  6. Performance metrics showing no regression from the refactoring

Detailed Implementation Guide

Step 1: Database Interface Implementation

Start by creating a clear abstraction for database operations:

#![allow(unused)]
fn main() {
/// Trait defining database operations
pub trait DatabaseOperations: Send + Sync {
    /// Get a value from the database
    async fn get(&self, collection: &str, key: &str) -> Result<Option<String>, ServiceError>;
    
    /// Set a value in the database
    async fn set(&self, collection: &str, key: &str, value: &str) -> Result<(), ServiceError>;
    
    /// Delete a value from the database
    async fn delete(&self, collection: &str, key: &str) -> Result<bool, ServiceError>;
    
    /// Query the database with a filter
    async fn query(&self, collection: &str, filter: &str) -> Result<Vec<String>, ServiceError>;
}

/// Trait for database providers
#[async_trait]
pub trait DatabaseProvider: Send + Sync {
    /// The type of database this provider creates
    type Database: DatabaseOperations;
    
    /// Create a new database instance
    async fn create_database(&self, config: DatabaseConfig) -> Result<Self::Database, ServiceError>;
    
    /// Check if this provider supports the given configuration
    fn supports(&self, config: &DatabaseConfig) -> bool;
}
}

Step 2: Health Indicator Implementation

Enhance the existing health indicator system:

#![allow(unused)]
fn main() {
/// Extended HealthIndicator trait
pub trait HealthIndicator: Send + Sync {
    /// Get the name of this health indicator
    fn name(&self) -> String;
    
    /// Check the health of this component
    fn check_health(&self, state: &Arc<AppState>) -> DependencyStatus;
    
    /// Get metadata about this indicator
    fn metadata(&self) -> HashMap<String, String> {
        HashMap::new()
    }
    
    /// Get the order in which this indicator should be checked
    fn order(&self) -> i32 {
        0
    }
    
    /// Whether this indicator is critical (failure means system is down)
    fn is_critical(&self) -> bool {
        false
    }
}

/// Health indicator provider trait
pub trait HealthIndicatorProvider: Send + Sync {
    /// Create health indicators for the application
    fn create_indicators(&self) -> Vec<Box<dyn HealthIndicator>>;
    
    /// Whether this provider is enabled
    fn is_enabled(&self, config: &AppConfig) -> bool;
}
}

Step 3: Cache Implementation

Abstract the cache implementation:

#![allow(unused)]
fn main() {
/// Cache operations trait
pub trait CacheOperations<T>: Send + Sync {
    /// Get a value from the cache
    async fn get(&self, key: &str) -> Option<T>;
    
    /// Set a value in the cache
    async fn set(&self, key: &str, value: T, ttl: Option<Duration>) -> Result<(), CacheError>;
    
    /// Delete a value from the cache
    async fn delete(&self, key: &str) -> Result<bool, CacheError>;
    
    /// Clear the cache
    async fn clear(&self) -> Result<(), CacheError>;
    
    /// Get cache statistics
    fn stats(&self) -> CacheStats;
}

/// Cache provider trait
#[async_trait]
pub trait CacheProvider: Send + Sync {
    /// Create a new cache instance
    async fn create_cache<T: Send + Sync + 'static>(
        &self, 
        config: CacheConfig
    ) -> Result<Box<dyn CacheOperations<T>>, CacheError>;
    
    /// Check if this provider supports the given configuration
    fn supports(&self, config: &CacheConfig) -> bool;
}
}

Testing Strategy

For each refactored service:

  1. Define interface tests that work with any implementation
  2. Create mock implementations for testing
  3. Test both success and error paths
  4. Add integration tests for real-world scenarios
  5. Benchmark before and after to ensure no performance regression

Example test for database interface:

#![allow(unused)]
fn main() {
#[tokio::test]
async fn test_database_interface() {
    // Create a mock database
    let db = MockDatabase::new();
    
    // Set expectations
    db.expect_get()
        .with(eq("users"), eq("1"))
        .returning(|_, _| Ok(Some("Alice".to_string())));
    
    // Test the interface
    let result = db.get("users", "1").await.unwrap();
    assert_eq!(result, Some("Alice".to_string()));
}
}

Performance Considerations

While making services generic, maintain performance by:

  1. Using static dispatch where possible
  2. Avoiding unnecessary boxing
  3. Minimizing indirection
  4. Using async properly
  5. Implementing efficient provider discovery

Packaging and Distribution

To enable easy adoption:

  1. Create separate crates for provider implementations
  2. Use feature flags for optional providers
  3. Provide examples for each implementation
  4. Include benchmarks in documentation