Files
foxhunt/AGENT_M10_ARCHITECTURE_MATRIX.md
jgrusewski 61801cfd06 feat(deprecation): Complete deprecated code analysis and cleanup preparation
**Wave D Phase 6 - Technical Debt Cleanup (Agent C6)**

## Changes
- Identified deprecated code patterns across codebase
- Analyzed mock repository usage (strategically retained per AGENT_M13)
- Documented deprecation cleanup strategy
- Prepared deprecation removal todos

## Analysis Results
- Mock structs: RETAINED (strategic testing infrastructure)
- Never-read fields: 2 instances in backtesting_service
- Dead code warnings: 35 total across workspace
- databento_old references: None found in active code

## Status
-  Deprecation analysis complete
-  Cleanup execution pending user confirmation
- 📊 Test impact assessment ready

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 00:46:19 +02:00

9.7 KiB

Cross-Service Repository Architecture Matrix

Quick Reference Table

Feature Trading Service Backtesting Service ML Training Service API Gateway
Uses Repositories Yes (4) Yes (3) Yes (1) No
Trait Count 4 4 1 0
Mock Pattern Inline #[cfg(test)] Dedicated structs Stateful RwLock N/A
Factory Method No Yes No N/A
Error Type Custom enum anyhow anyhow N/A
Composition Direct SQLx Provider wrapper Delegation N/A
DI Pattern Constructor Factory trait Constructor N/A
Test Support Fair Excellent Excellent N/A
Production Ready Yes Yes Yes Yes
Reference Impl No YES No N/A

Code Metrics

Metric Trading Backtesting ML Training Total
Trait LOC 319 301 63 683
Impl LOC 1,448 365 230 2,043
Mock LOC Varies 112 102 214+
Total LOC 1,767 666+ 293 2,726+
Impl Count 4 4 1 9
Traits/Impl 1:1 1:1 1:1 3:9

Decision Tree: Which Service to Model After?

Do you need multiple repositories?
  ├─ Yes → Use Backtesting Service pattern
  │        (factory, combined trait, dedicated mocks)
  │
  └─ No → Use ML Training Service pattern
           (single repo, direct injection, stateful mock)

Do you need environment-based selection?
  ├─ Yes → Use Backtesting Service pattern with create_repositories()
  │
  └─ No → Either pattern is fine

Do you need real data + mocks at runtime?
  ├─ Yes → Use Backtesting Service pattern
  │        (supports both via factory)
  │
  └─ No → Either pattern is acceptable

Do you need production-like test behavior?
  ├─ Yes → Use ML Training pattern
  │        (stateful RwLock mocks)
  │
  └─ No → Use Backtesting pattern
           (simple empty-return mocks)

Repository Trait Checklist

When creating a new repository trait, ensure:

  • #[async_trait] macro applied
  • Send + Sync bounds included
  • All methods are async
  • Return type is Result<T> (anyhow or custom)
  • Methods are fine-grained (single responsibility)
  • No database-specific concerns leak into trait
  • Trait is pub for use across crates
  • Documented with examples

Example:

/// Repository trait for domain-specific operations
#[async_trait]
pub trait MyRepository: Send + Sync {
    /// Descriptive method documentation
    async fn operation(&self, param: Type) -> Result<ReturnType>;
}

Implementation Patterns Comparison

Pattern A: Direct Database (Trading Service)

Business Logic
    ↓
PostgresTradingRepository (impl TradingRepository trait)
    ↓
SQLx (direct database queries)
    ↓
PostgreSQL
  • Pros: Simple, straightforward
  • Cons: Tight coupling to database
  • Use: When you control the entire data access layer
Business Logic
    ↓
StorageManagerTradingRepository (impl TradingRepository trait)
    ↓
StorageManager (business logic)
    ↓
Database/Provider (flexible)
  • Pros: Loose coupling, flexible, testable, factory-friendly
  • Cons: Extra abstraction layer
  • Use: New services, multiple data sources, complex logic

Pattern C: Delegation (ML Training Service)

Business Logic
    ↓
PostgresMlDataRepository (impl MlDataRepository trait)
    ↓
DatabaseManager (delegates all calls)
    ↓
SQLx (direct queries via DatabaseManager)
  • Pros: Simple, but still abstracted
  • Cons: Minimal value add over direct access
  • Use: When DatabaseManager already exists

Error Handling Comparison

Trading Service (Custom)

pub type TradingServiceResult<T> = Result<T, TradingServiceError>;

pub enum TradingServiceError {
    ValidationError { message: String },
    TimestampConversion { timestamp: i64 },
    NetworkError { details: String },
    // ...
}

// Usage
async fn store_order(&self, order: &TradingOrder) -> TradingServiceResult<String> {
    Ok(id)
}
  • Pros: Type-safe, custom error handling
  • Cons: Diverges from codebase standard
  • Status: NEEDS MIGRATION to anyhow

Backtesting/ML Training (anyhow)

pub type Result<T> = anyhow::Result<T>;

// Usage
async fn create_training_job(&self, job: &TrainingJobRecord) -> Result<()> {
    Ok(())
}
  • Pros: Standardized, composable, ergonomic
  • Cons: Less type-safe error handling
  • Status: STANDARD for new code

Dependency Injection Comparison

Direct Constructor Injection

pub struct PostgresMlDataRepository {
    database: Arc<DatabaseManager>,
}

impl PostgresMlDataRepository {
    pub fn new(database: Arc<DatabaseManager>) -> Self {
        Self { database }
    }
}

// Usage
let repo = Arc::new(PostgresMlDataRepository::new(db_manager));

Factory Function with Environment Selection

pub async fn create_repositories(storage: Arc<StorageManager>) -> Result<DefaultRepositories> {
    let use_dbn = std::env::var("USE_DBN_DATA")
        .ok()
        .and_then(|v| v.parse::<bool>().ok())
        .unwrap_or(false);
    
    let market_data: Box<dyn MarketDataRepository> = if use_dbn {
        Box::new(DbnMarketDataRepository::new_with_mappings(mappings).await?)
    } else {
        Box::new(DataProviderMarketDataRepository::new().await?)
    };
    
    Ok(DefaultRepositories { market_data, ... })
}

// Usage
let repos = create_repositories(storage).await?;

Recommendation: Use factory for services with multiple implementations or env-based selection

Mock Strategy Comparison

Approach A: Inline Mocks (Trading)

#[cfg(test)]
mod tests {
    // Mocks defined inline when needed
    // Only available during testing
}
  • Isolation: High
  • Reusability: Low
  • Boilerplate: Minimal
pub struct MockMarketDataRepository;

#[async_trait]
impl MarketDataRepository for MockMarketDataRepository {
    async fn load_historical_data(...) -> Result<Vec<MarketData>> {
        Ok(vec![])
    }
}
  • Isolation: Low
  • Reusability: High
  • Boilerplate: Moderate
  • Advantage: Can be used in production if needed (feature flag)

Approach C: Stateful In-Memory (ML Training)

#[cfg(test)]
pub struct MockMlDataRepository {
    jobs: Arc<RwLock<HashMap<Uuid, TrainingJobRecord>>>,
}

#[cfg(test)]
#[async_trait]
impl MlDataRepository for MockMlDataRepository {
    async fn create_training_job(&self, job: &TrainingJobRecord) -> Result<()> {
        let mut jobs = self.jobs.write().await;
        jobs.insert(job.id, job.clone());
        Ok(())
    }
}
  • Isolation: Medium
  • Reusability: Medium
  • Boilerplate: High
  • Advantage: Realistic test behavior, state verification

Best Practice: Use Approach B for new services

Migration Path for Trading Service

To align Trading Service with Backtesting/ML patterns:

Step 1: Error Type Migration

// Before
pub type TradingServiceResult<T> = Result<T, TradingServiceError>;

// After
pub type Result<T> = anyhow::Result<T>;

Step 2: Mock Pattern Migration

// Before: Inline in tests

// After: Dedicated mock structs
pub struct MockTradingRepository;

#[async_trait]
impl TradingRepository for MockTradingRepository { ... }

Step 3: Consider Factory Pattern

// If multiple repository implementations exist,
// add factory function for DI:
pub async fn create_repositories(pool: PgPool) -> Result<TradingRepositories> {
    let trading = Box::new(PostgresTradingRepository::new(pool));
    let market_data = Box::new(PostgresMarketDataRepository::new(pool));
    // ...
}

Effort: 2-3 hours, low risk, high value

Testing Strategy by Service

Trading Service

Unit Tests (per repository impl)
    ↓
Integration Tests (services + repos)
    ↓
E2E Tests (full stack)

Mock Support: Inline mocks (added per test)
Unit Tests (mock repositories with empty returns)
    ↓
Integration Tests (real repositories + providers)
    ↓
E2E Tests (end-to-end backtesting runs)

Mock Support: Dedicated mock structs + factory selection

ML Training Service

Unit Tests (stateful in-memory mock repositories)
    ↓
Integration Tests (real postgres repository)
    ↓
E2E Tests (full training pipeline)

Mock Support: Full in-memory HashMap-backed mock

API Gateway (Why No Repositories)

API Gateway correctly does NOT use repositories because:

  1. Network Boundary: Gateway is the entry point, not data layer
  2. Stateless Design: Should not hold business state
  3. Proxy Pattern: Routes to downstream services
  4. Separation of Concerns:
    • ✓ Authentication, authorization
    • ✓ Rate limiting, audit logging
    • ✓ Request routing, health checks
    • ✗ Data persistence, business logic

Example correct structure:

// API Gateway
pub struct ApiGateway {
    trading_client: TradingServiceClient,
    backtesting_client: BacktestingServiceClient,
    ml_training_client: MlTrainingServiceClient,
    auth: AuthLayer,
    rate_limiter: RateLimiter,
}

// NO repositories here
// Each downstream service manages its own data

Summary: Quick Decision Guide

For new services:

  1. Choose Backtesting Service as template
  2. Use dedicated mock structs
  3. Use factory pattern for DI if multiple repos exist
  4. Standardize on anyhow::Result
  5. Add factory function for environment-based selection

For existing services:

  1. Keep current implementation (already working)
  2. Document patterns in REPOSITORY_PATTERN.md
  3. Plan gradual migration to backtesting patterns
  4. Start with error type standardization (low risk)

For API Gateway:

  • Keep as-is (correct by design, no repositories)