**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>
374 lines
9.7 KiB
Markdown
374 lines
9.7 KiB
Markdown
# 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:
|
|
```rust
|
|
/// 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)
|
|
```rust
|
|
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)
|
|
```rust
|
|
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
|
|
```rust
|
|
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
|
|
```rust
|
|
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)
|
|
```rust
|
|
#[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
|
|
```rust
|
|
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)
|
|
```rust
|
|
#[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
|
|
```rust
|
|
// Before
|
|
pub type TradingServiceResult<T> = Result<T, TradingServiceError>;
|
|
|
|
// After
|
|
pub type Result<T> = anyhow::Result<T>;
|
|
```
|
|
|
|
### Step 2: Mock Pattern Migration
|
|
```rust
|
|
// Before: Inline in tests
|
|
|
|
// After: Dedicated mock structs
|
|
pub struct MockTradingRepository;
|
|
|
|
#[async_trait]
|
|
impl TradingRepository for MockTradingRepository { ... }
|
|
```
|
|
|
|
### Step 3: Consider Factory Pattern
|
|
```rust
|
|
// 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:
|
|
|
|
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:
|
|
```rust
|
|
// 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<T>
|
|
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)
|