FOSSIL (S175): This refactoring was executed.
byob_impl.rswas split intobyob_impl/directory module withdeployment_lifecycle.rs,network_manager.rs,health_monitor.rs, and test submodules. File path and line counts below are historical. Retained as pattern reference for future refactoring.
File: crates/core/toadstool/src/byob/byob_impl/ (was byob_impl.rs)
Original Size: 927 lines
Status: ✅ Executed
Goal: Apply trait-based composition to separate concerns and improve maintainability
Not a Goal: Blindly split into smaller files (we want architectural improvements)
| Concern | Lines | Functions | Complexity |
|---|---|---|---|
| Service Execution | ~250 | 3 | High |
| Network Management | ~220 | 3 | Medium |
| Health Monitoring | ~150 | 2 | Medium |
| Resource Management | ~100 | 2 | Low |
| Lifecycle (Coordinator) | ~150 | 5 | Medium |
| Tests | ~57 | 5 | Low |
Total: 927 lines
Current State: ✅ Good (not bad code!)
- Clear separation of concerns in function names
- Well-documented
- Proper error handling
- Tests included
Why Refactor:
- Easier testing (mock individual concerns)
- Better reusability (traits can be implemented by other types)
- Clearer responsibilities (single responsibility per trait)
- Reduced cognitive load (smaller files, focused logic)
// BEFORE: All in one impl block
impl ByobComputeExecutor {
fn create_service_execution_request(...) { /* ... */ }
async fn execute_services(...) { /* ... */ }
fn create_deployment_network(...) { /* ... */ }
fn allocate_external_ip(...) { /* ... */ }
async fn monitor_deployment_health(...) { /* ... */ }
fn perform_health_check(...) { /* ... */ }
async fn update_resource_usage(...) { /* ... */ }
async fn stop_service_execution(...) { /* ... */ }
// + 5 lifecycle methods
}
// AFTER: Trait-based composition
trait ServiceExecutor {
fn create_service_execution_request(...) -> Result<ExecutionRequest>;
async fn execute_services(...) -> Result<()>;
async fn stop_service_execution(...) -> Result<()>;
}
trait NetworkManager {
fn create_deployment_network(...) -> NetworkInfo;
fn allocate_external_ip(...) -> Option<String>;
}
trait HealthMonitor {
async fn monitor_deployment_health(...) -> Result<()>;
fn perform_health_check(...) -> Result<bool>;
}
trait ResourceManager {
async fn update_resource_usage(...) -> Result<()>;
async fn get_resource_usage(...) -> Result<ResourceUsage>;
}
// Coordinator stays in main impl (lifecycle only)
impl ByobComputeExecutor {
// Coordination methods use traits
async fn deploy_biome(&self, ...) -> Result<Response> {
// Uses: ServiceExecutor, NetworkManager, ResourceManager
}
async fn stop_deployment(&self, ...) -> Result<()> {
// Uses: ServiceExecutor
}
}
// Trait implementations in separate files
impl ServiceExecutor for ByobComputeExecutor { /* ... */ }
impl NetworkManager for ByobComputeExecutor { /* ... */ }
impl HealthMonitor for ByobComputeExecutor { /* ... */ }
impl ResourceManager for ByobComputeExecutor { /* ... */ }crates/core/toadstool/src/byob/
├── byob_impl.rs (~400 lines) - Coordinator + lifecycle
├── service_executor.rs (~250 lines) - Service execution trait + impl
├── network_manager.rs (~220 lines) - Network management trait + impl
├── health_monitor.rs (~150 lines) - Health monitoring trait + impl
├── resource_manager.rs (~100 lines) - Resource tracking trait + impl
├── byob_types.rs (existing)
├── config.rs (existing)
├── deployment.rs (existing)
├── validation.rs (existing)
└── mod.rs (updated exports)
| File | Before | After | Change |
|---|---|---|---|
| byob_impl.rs | 927 | ~400 | -57% |
| New: service_executor.rs | 0 | ~250 | +250 |
| New: network_manager.rs | 0 | ~220 | +220 |
| New: health_monitor.rs | 0 | ~150 | +150 |
| New: resource_manager.rs | 0 | ~100 | +100 |
| Total | 927 | 1120 | +193 |
Note: Line count increases (good thing!) - we gain:
- 4 trait definitions (~40 lines total)
- Better documentation per trait (~80 lines)
- More modular structure
Purpose: Handle service execution lifecycle
Methods:
#[async_trait]
pub trait ServiceExecutor: Send + Sync {
/// Create execution request for a service
fn create_service_execution_request(
&self,
service: &ServiceSpec,
deployment_id: Uuid,
) -> ToadStoolResult<ExecutionRequest>;
/// Execute all services in a deployment
async fn execute_services(
&self,
deployment: &mut ActiveDeployment,
) -> ToadStoolResult<()>;
/// Stop a specific service execution
async fn stop_service_execution(
&self,
service_name: String,
execution_id: Uuid,
) -> ToadStoolResult<()>;
}Benefits:
- Can mock for testing (inject fake ServiceExecutor)
- Can reuse in other executor types
- Clear contract for service management
Purpose: Handle networking and IP allocation
Methods:
pub trait NetworkManager: Send + Sync {
/// Create network configuration for deployment
fn create_deployment_network(
&self,
deployment: &ByobDeploymentRequest,
) -> NetworkInfo;
/// Allocate external IP for a service
fn allocate_external_ip(
&self,
service_spec: &ServiceSpec,
team_id: &str,
) -> Option<String>;
}Benefits:
- Can swap implementations (different network backends)
- Easy to test networking logic in isolation
- Clear boundary for network-related code
Purpose: Monitor deployment and service health
Methods:
#[async_trait]
pub trait HealthMonitor: Send + Sync {
/// Monitor overall deployment health
async fn monitor_deployment_health(
&self,
deployment_id: Uuid,
) -> ToadStoolResult<()>;
/// Perform health check for a specific service
fn perform_health_check(
&self,
service_name: &str,
health_check: &HealthCheck,
) -> ToadStoolResult<bool>;
}Benefits:
- Can mock health checks for testing
- Can implement different health strategies
- Clear separation of monitoring logic
Purpose: Track and report resource usage
Methods:
#[async_trait]
pub trait ResourceManager: Send + Sync {
/// Update resource usage for a deployment
async fn update_resource_usage(
&self,
deployment_id: Uuid,
) -> ToadStoolResult<()>;
/// Get current resource usage
async fn get_resource_usage(
&self,
deployment_id: Uuid,
) -> ToadStoolResult<ResourceUsage>;
}Benefits:
- Can implement different tracking strategies
- Easy to add caching layer
- Clear boundary for resource-related code
Step 1: Create trait definitions
# Create new files with trait definitions only
touch crates/core/toadstool/src/byob/service_executor.rs
touch crates/core/toadstool/src/byob/network_manager.rs
touch crates/core/toadstool/src/byob/health_monitor.rs
touch crates/core/toadstool/src/byob/resource_manager.rsStep 2: Add traits to mod.rs
pub mod service_executor;
pub mod network_manager;
pub mod health_monitor;
pub mod resource_manager;Step 3: Implement traits in byob_impl.rs
impl ServiceExecutor for ByobComputeExecutor {
// Copy existing methods here
}
// ... same for other traitsStep 4: Test (should compile with zero behavior changes)
cargo test --package toadstoolStep 1: Move trait impls to dedicated files
- Cut impl blocks from byob_impl.rs
- Paste into respective trait files
Step 2: Update imports in byob_impl.rs
use super::service_executor::ServiceExecutor;
use super::network_manager::NetworkManager;
// ...Step 3: Test again
cargo test --package toadstoolStep 1: Remove #[allow(dead_code)] as methods are used
Step 2: Add more tests for individual traits
Step 3: Document each trait thoroughly
#[tokio::test]
async fn test_deploy_biome() {
let executor = ByobComputeExecutor::new(...);
let response = executor.deploy_biome(request).await?;
assert!(response.is_successful());
}// Test individual concerns in isolation
#[test]
fn test_network_manager_ip_allocation() {
let executor = ByobComputeExecutor::new(...);
let ip = executor.allocate_external_ip(&service, "team-1");
assert_eq!(ip, Some("203.0.113.42".to_string()));
}
#[tokio::test]
async fn test_service_executor_executes_services() {
let executor = ByobComputeExecutor::new(...);
let mut deployment = ActiveDeployment::new(...);
executor.execute_services(&mut deployment).await?;
assert_eq!(deployment.status, DeploymentStatus::Running);
}
// Integration test still works
#[tokio::test]
async fn test_deploy_biome_integration() {
let executor = ByobComputeExecutor::new(...);
let response = executor.deploy_biome(request).await?;
assert!(response.is_successful());
}Benefits:
- Faster unit tests (no full deployment needed)
- Better error isolation (know exactly which component failed)
- Easier mocking (mock just what you need)
| Metric | Before | After | Change |
|---|---|---|---|
| Lines per file | 927 | ~400 | -57% |
| Cognitive complexity | High | Medium | ↓ |
| Test granularity | Coarse | Fine | ↑ |
| Reusability | Low | High | ↑ |
| Mock-ability | Hard | Easy | ↑ |
Before:
- "Where's the network code?" → Search 927 lines
- "How do I test IP allocation?" → Full deployment needed
- "Can I reuse health checks?" → Tightly coupled
After:
- "Where's the network code?" →
network_manager.rs - "How do I test IP allocation?" → Unit test NetworkManager
- "Can I reuse health checks?" → impl HealthMonitor for MyType
Risk: Refactoring breaks existing code
Mitigation:
- Keep public API identical (ByobExecutor trait unchanged)
- Run full test suite after each phase
- Use compiler to catch issues (Rust's type system helps!)
Risk: Too many traits, too complex
Mitigation:
- Only 4 traits (manageable)
- Each trait has clear, focused purpose
- All traits are actively used by coordinator
Risk: Trait dispatch adds overhead
Mitigation:
- Traits are monomorphized (zero runtime cost in most cases)
- Benchmark before/after (expect < 1% difference)
- Profile if needed
Refactoring is successful if:
- ✅ All tests pass (zero behavior changes)
- ✅ byob_impl.rs < 500 lines (coordinator only)
- ✅ Each trait file < 300 lines (focused concerns)
- ✅ Zero clippy warnings
- ✅ Improved test granularity (can test traits in isolation)
- ✅ Better documentation (per-trait docs)
This pattern applies to:
crates/runtime/universal/src/backends/podman.rs(1038 lines)crates/barracuda/src/device/wgpu_device.rs(891 lines)- Any file > 800 lines with multiple concerns
Pattern:
- Identify distinct concerns (2-5 concerns)
- Create traits for each concern
- Keep coordinator in main file
- Move implementations to trait files
- Test each trait in isolation
- Document benefits
Document: docs/architecture/REFACTORING_PATTERN_BYOB.md
Status: ✅ Analysis Complete | 🔄 Ready to Implement
Est. Time: 4-6 hours
Risk Level: Low (can be done incrementally)
Let's refactor smart, not just split files! 🚀