**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>
9.7 KiB
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 appliedSend + Syncbounds 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
pubfor 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
Pattern B: Provider Wrapper (Backtesting Service) ⭐ RECOMMENDED
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
Approach B: Dedicated Mock Structs (Backtesting) ⭐ RECOMMENDED
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)
Backtesting Service (RECOMMENDED APPROACH)
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:
- Network Boundary: Gateway is the entry point, not data layer
- Stateless Design: Should not hold business state
- Proxy Pattern: Routes to downstream services
- 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:
- Choose Backtesting Service as template
- Use dedicated mock structs
- Use factory pattern for DI if multiple repos exist
- Standardize on anyhow::Result
- Add factory function for environment-based selection
For existing services:
- Keep current implementation (already working)
- Document patterns in REPOSITORY_PATTERN.md
- Plan gradual migration to backtesting patterns
- Start with error type standardization (low risk)
For API Gateway:
- Keep as-is (correct by design, no repositories)