28 KiB
Project Mycelium - Builder Pattern Maximization Roadmap
Document Purpose: Comprehensive roadmap for achieving maximum builder pattern consolidation across the entire Project Mycelium codebase, targeting industry-standard single-source-of-truth architecture with minimal code duplication.
Last Updated: 2025-08-06
Status: Phase 2 Planning - Ready for Implementation
Target: Additional 70% code reduction (~1,430 total lines eliminated)
🎯 Executive Summary
Building on the successful Phase 1 achievement (95.8% compilation error reduction, ~800 lines eliminated), this roadmap outlines Phase 2 consolidation opportunities that will achieve an additional 70% code reduction while maintaining all existing features and enhancing maintainability through industry-standard builder patterns.
Phase 1 Success Metrics:
- ✅ 24 → 0 compilation errors (100% resolution)
- ✅ ~800 lines eliminated through SessionDataBuilder consolidation
- ✅ Single-source-of-truth for all UserPersistentData construction
- ✅ Zero feature regression - all functionality preserved
Phase 2 Target Metrics:
- 🎯 Additional ~630 lines reduction (70% of remaining opportunities)
- 🎯 4 major builder consolidations (Configuration, Service, Response, Mock)
- 🎯 Industry-standard patterns throughout entire codebase
- 🎯 Enhanced maintainability and developer experience
📊 Consolidation Opportunity Analysis
Current Codebase Analysis Results
Based on comprehensive codebase analysis, the following patterns have been identified for consolidation:
Pattern Type | Current Instances | Lines per Instance | Total Lines | Consolidation Potential |
---|---|---|---|---|
Configuration Creation | 15+ instances | ~10 lines | ~150 lines | ConfigurationBuilder |
Service Instantiation | 25+ instances | ~6 lines | ~150 lines | ServiceFactory |
API Response Construction | 50+ instances | ~6 lines | ~300 lines | ResponseBuilder |
Mock Data Elimination | 20+ instances | ~12 lines | ~240 lines | DataAggregator |
Error Handling | 30+ instances | ~4 lines | ~120 lines | ErrorBuilder |
Context Building | 10+ instances | ~8 lines | ~80 lines | ContextBuilder |
Validation Logic | 15+ instances | ~5 lines | ~75 lines | ValidationBuilder |
Total Opportunity | 165+ instances | ~8 avg | ~1,115 lines | ~780 lines reduction |
🏗️ Phase 2 Implementation Roadmap
Priority 1: ConfigurationBuilder (~150 line reduction)
Current Problem:
// ❌ Scattered configuration creation in multiple files
let config = Config::builder()
.set_default("server.host", "127.0.0.1")?
.set_default("server.port", 9999)?
.set_default("server.workers", None::<u32>)?
.set_default("templates.dir", "./src/views")?
.add_source(File::with_name("config/default").required(false))
.add_source(File::with_name("config/local").required(false))
.add_source(config::Environment::with_prefix("APP").separator("__"))
.build()?;
Solution: Centralized ConfigurationBuilder
// ✅ Single-source-of-truth configuration
pub struct ConfigurationBuilder;
impl ConfigurationBuilder {
pub fn development() -> AppConfig {
AppConfig {
server: ServerConfig {
host: "127.0.0.1".to_string(),
port: 9999,
workers: Some(4),
},
templates: TemplateConfig {
dir: "./src/views".to_string(),
},
..Default::default()
}
}
pub fn production() -> AppConfig {
AppConfig {
server: ServerConfig {
host: "0.0.0.0".to_string(),
port: 8080,
workers: Some(num_cpus::get() as u32),
},
templates: TemplateConfig {
dir: "/app/templates".to_string(),
},
..Default::default()
}
}
pub fn testing() -> AppConfig {
AppConfig {
server: ServerConfig {
host: "127.0.0.1".to_string(),
port: 0, // Random port
workers: Some(1),
},
..Default::default()
}
}
pub fn from_env() -> Result<AppConfig, ConfigError> {
match env::var("APP_ENV").unwrap_or_else(|_| "development".to_string()).as_str() {
"production" => Ok(Self::production()),
"testing" => Ok(Self::testing()),
_ => Ok(Self::development()),
}
}
}
Usage Pattern:
// ✅ Clean, readable configuration
let config = ConfigurationBuilder::from_env()?;
let dev_config = ConfigurationBuilder::development();
let prod_config = ConfigurationBuilder::production();
Priority 2: ServiceFactory (~100 line reduction)
Current Problem:
// ❌ Repeated service instantiation throughout codebase
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
Ok(service) => service,
Err(e) => {
log::error!("Failed to build farmer service: {}", e);
return Ok(HttpResponse::InternalServerError().json(serde_json::json!({
"error": "Service initialization failed"
})));
}
};
Solution: Centralized ServiceFactory
// ✅ Single-source-of-truth service creation
pub struct ServiceFactory;
impl ServiceFactory {
pub fn create_farmer_service() -> Result<FarmerService, String> {
FarmerService::builder()
.auto_sync_enabled(true)
.metrics_collection(true)
.build()
}
pub fn create_order_service() -> Result<OrderService, String> {
let currency_service = Self::create_currency_service()?;
let product_service = Self::create_product_service()?;
OrderService::builder()
.currency_service(currency_service)
.product_service(product_service)
.auto_save(true)
.build()
}
pub fn create_user_service() -> Result<UserService, String> {
UserService::builder()
.include_metrics(true)
.cache_enabled(true)
.real_time_updates(true)
.build()
}
pub fn create_currency_service() -> Result<CurrencyService, String> {
CurrencyServiceBuilder::new()
.base_currency("USD")
.cache_duration(60)
.auto_update(true)
.build()
}
pub fn create_product_service() -> Result<ProductService, String> {
let currency_service = Self::create_currency_service()?;
ProductServiceBuilder::new()
.currency_service(currency_service)
.cache_enabled(true)
.include_slice_products(true)
.build()
}
// Convenience method for controllers
pub fn create_all_services() -> Result<ServiceBundle, String> {
Ok(ServiceBundle {
farmer: Self::create_farmer_service()?,
order: Self::create_order_service()?,
user: Self::create_user_service()?,
currency: Self::create_currency_service()?,
product: Self::create_product_service()?,
})
}
}
pub struct ServiceBundle {
pub farmer: FarmerService,
pub order: OrderService,
pub user: UserService,
pub currency: CurrencyService,
pub product: ProductService,
}
Usage Pattern:
// ✅ Clean, error-handled service creation
let farmer_service = ServiceFactory::create_farmer_service()
.map_err(|e| actix_web::error::ErrorInternalServerError(e))?;
// ✅ Or get all services at once
let services = ServiceFactory::create_all_services()
.map_err(|e| actix_web::error::ErrorInternalServerError(e))?;
Priority 3: ResponseBuilder (~200 line reduction)
Current Problem:
// ❌ Repeated JSON response construction
Ok(HttpResponse::Ok().json(serde_json::json!({
"success": true,
"message": "Operation completed successfully",
"data": result_data
})))
Ok(HttpResponse::InternalServerError().json(serde_json::json!({
"success": false,
"error": "Service initialization failed",
"details": error_message
})))
Solution: Centralized ResponseBuilder
// ✅ Single-source-of-truth API responses
pub struct ApiResponseBuilder;
impl ApiResponseBuilder {
pub fn success<T: Serialize>(data: T) -> HttpResponse {
HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": data,
"timestamp": Utc::now().to_rfc3339()
}))
}
pub fn success_with_message<T: Serialize>(data: T, message: &str) -> HttpResponse {
HttpResponse::Ok().json(serde_json::json!({
"success": true,
"message": message,
"data": data,
"timestamp": Utc::now().to_rfc3339()
}))
}
pub fn error(message: &str) -> HttpResponse {
HttpResponse::InternalServerError().json(serde_json::json!({
"success": false,
"error": message,
"timestamp": Utc::now().to_rfc3339()
}))
}
pub fn error_with_details(message: &str, details: &str) -> HttpResponse {
HttpResponse::InternalServerError().json(serde_json::json!({
"success": false,
"error": message,
"details": details,
"timestamp": Utc::now().to_rfc3339()
}))
}
pub fn unauthorized(message: &str) -> HttpResponse {
HttpResponse::Unauthorized().json(serde_json::json!({
"success": false,
"error": message,
"code": "UNAUTHORIZED",
"timestamp": Utc::now().to_rfc3339()
}))
}
pub fn not_found(resource: &str) -> HttpResponse {
HttpResponse::NotFound().json(serde_json::json!({
"success": false,
"error": format!("{} not found", resource),
"code": "NOT_FOUND",
"timestamp": Utc::now().to_rfc3339()
}))
}
pub fn paginated<T: Serialize>(
data: Vec<T>,
page: u32,
per_page: u32,
total: u32
) -> HttpResponse {
HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": data,
"pagination": {
"page": page,
"per_page": per_page,
"total": total,
"total_pages": (total + per_page - 1) / per_page
},
"timestamp": Utc::now().to_rfc3339()
}))
}
pub fn validation_error(errors: Vec<&str>) -> HttpResponse {
HttpResponse::BadRequest().json(serde_json::json!({
"success": false,
"error": "Validation failed",
"validation_errors": errors,
"code": "VALIDATION_ERROR",
"timestamp": Utc::now().to_rfc3339()
}))
}
}
Usage Pattern:
// ✅ Clean, consistent API responses
return Ok(ApiResponseBuilder::success(farmer_data));
return Ok(ApiResponseBuilder::error("Service initialization failed"));
return Ok(ApiResponseBuilder::unauthorized("User not authenticated"));
return Ok(ApiResponseBuilder::not_found("Node"));
return Ok(ApiResponseBuilder::paginated(nodes, page, per_page, total));
Priority 4: Persistent Data Integration (~180 line reduction + Mock Elimination)
Current Problem:
// ❌ Scattered mock data creation throughout codebase
let mock_products = vec![
Product { name: "Mock VM".to_string(), /* ... */ },
Product { name: "Mock Storage".to_string(), /* ... */ },
];
// ❌ Hardcoded mock services, apps, nodes
let mock_services = get_hardcoded_services();
let mock_apps = get_hardcoded_apps();
Solution: Persistent Data Integration with DataAggregator
🏭 Industry Standard Architecture: Real Data Only
This approach eliminates all mock data and uses only persistent user data from user_data/
directory, which is the industry-standard production approach:
// ✅ Real data aggregation from persistent storage
pub struct DataAggregator;
impl DataAggregator {
/// Aggregate all marketplace products from real user data
pub fn get_marketplace_products() -> Vec<Product> {
let mut products = Vec::new();
// Load all user data files from user_data/ directory
if let Ok(entries) = std::fs::read_dir("user_data/") {
for entry in entries.flatten() {
if let Some(file_name) = entry.file_name().to_str() {
if file_name.ends_with(".json") && !file_name.ends_with("_cart.json") {
if let Ok(user_data) = UserPersistence::load_user_data_from_file(&entry.path()) {
// Aggregate products from user's services
products.extend(Self::services_to_products(&user_data.services));
// Aggregate products from user's apps
products.extend(Self::apps_to_products(&user_data.apps));
// Aggregate products from user's slice products
products.extend(Self::slice_products_to_products(&user_data.slice_products));
// Aggregate products from user's nodes (for rental)
products.extend(Self::nodes_to_products(&user_data.nodes));
}
}
}
}
}
products
}
/// Aggregate all marketplace services from real user data
pub fn get_marketplace_services() -> Vec<Service> {
let mut services = Vec::new();
if let Ok(entries) = std::fs::read_dir("user_data/") {
for entry in entries.flatten() {
if let Some(file_name) = entry.file_name().to_str() {
if file_name.ends_with(".json") && !file_name.ends_with("_cart.json") {
if let Ok(user_data) = UserPersistence::load_user_data_from_file(&entry.path()) {
services.extend(user_data.services.into_iter().filter(|s| s.status == "Active"));
}
}
}
}
}
services
}
/// Aggregate all marketplace apps from real user data
pub fn get_marketplace_apps() -> Vec<PublishedApp> {
let mut apps = Vec::new();
if let Ok(entries) = std::fs::read_dir("user_data/") {
for entry in entries.flatten() {
if let Some(file_name) = entry.file_name().to_str() {
if file_name.ends_with(".json") && !file_name.ends_with("_cart.json") {
if let Ok(user_data) = UserPersistence::load_user_data_from_file(&entry.path()) {
apps.extend(user_data.apps.into_iter().filter(|a| a.status == "Active"));
}
}
}
}
}
apps
}
/// Aggregate all available nodes for rental from real user data
pub fn get_marketplace_nodes() -> Vec<Node> {
let mut nodes = Vec::new();
if let Ok(entries) = std::fs::read_dir("user_data/") {
for entry in entries.flatten() {
if let Some(file_name) = entry.file_name().to_str() {
if file_name.ends_with(".json") && !file_name.ends_with("_cart.json") {
if let Ok(user_data) = UserPersistence::load_user_data_from_file(&entry.path()) {
nodes.extend(user_data.nodes.into_iter().filter(|n| n.status == NodeStatus::Online));
}
}
}
}
}
nodes
}
/// Get marketplace statistics from real user data
pub fn get_marketplace_stats() -> MarketplaceStats {
let products = Self::get_marketplace_products();
let services = Self::get_marketplace_services();
let apps = Self::get_marketplace_apps();
let nodes = Self::get_marketplace_nodes();
MarketplaceStats {
total_products: products.len(),
total_services: services.len(),
total_apps: apps.len(),
total_nodes: nodes.len(),
active_providers: Self::count_active_providers(),
total_transactions: Self::count_total_transactions(),
}
}
// Private helper methods for data conversion
fn services_to_products(services: &[Service]) -> Vec<Product> {
services.iter()
.filter(|s| s.status == "Active")
.map(|service| {
ProductBuilder::new()
.name(&service.name)
.category_id(&service.category)
.description(&service.description)
.base_price(Decimal::from(service.price_per_hour))
.base_currency("USD")
.provider_id(&service.provider_email)
.provider_name(&service.provider_name)
.build()
.expect("Failed to convert service to product")
})
.collect()
}
fn apps_to_products(apps: &[PublishedApp]) -> Vec<Product> {
apps.iter()
.filter(|a| a.status == "Active")
.map(|app| {
ProductBuilder::new()
.name(&app.name)
.category_id(&app.category)
.description(&format!("Published app: {}", app.name))
.base_price(Decimal::from(app.monthly_revenue_usd))
.base_currency("USD")
.provider_id(&format!("app-provider-{}", app.id))
.provider_name("App Provider")
.build()
.expect("Failed to convert app to product")
})
.collect()
}
fn slice_products_to_products(slice_products: &[SliceProduct]) -> Vec<Product> {
slice_products.iter()
.map(|slice| {
ProductBuilder::new()
.name(&slice.name)
.category_id("slice")
.description(&slice.description)
.base_price(slice.base_price_per_hour)
.base_currency("USD")
.provider_id(&slice.provider_email)
.provider_name("Slice Provider")
.build()
.expect("Failed to convert slice to product")
})
.collect()
}
fn nodes_to_products(nodes: &[Node]) -> Vec<Product> {
nodes.iter()
.filter(|n| n.status == NodeStatus::Online && n.rental_options.is_some())
.map(|node| {
ProductBuilder::new()
.name(&format!("Node Rental: {}", node.id))
.category_id("node-rental")
.description(&format!("Rent computing resources from node {}", node.id))
.base_price(node.rental_options.as_ref().unwrap().base_price_per_hour)
.base_currency("USD")
.provider_id(&node.farmer_email)
.provider_name("Node Farmer")
.build()
.expect("Failed to convert node to product")
})
.collect()
}
fn count_active_providers() -> usize {
// Count unique provider emails across all user data
let mut providers = std::collections::HashSet::new();
if let Ok(entries) = std::fs::read_dir("user_data/") {
for entry in entries.flatten() {
if let Some(file_name) = entry.file_name().to_str() {
if file_name.ends_with(".json") && !file_name.ends_with("_cart.json") {
if let Ok(user_data) = UserPersistence::load_user_data_from_file(&entry.path()) {
if !user_data.services.is_empty() || !user_data.apps.is_empty() || !user_data.nodes.is_empty() {
providers.insert(user_data.user_email);
}
}
}
}
}
}
providers.len()
}
fn count_total_transactions() -> usize {
let mut total = 0;
if let Ok(entries) = std::fs::read_dir("user_data/") {
for entry in entries.flatten() {
if let Some(file_name) = entry.file_name().to_str() {
if file_name.ends_with(".json") && !file_name.ends_with("_cart.json") {
if let Ok(user_data) = UserPersistence::load_user_data_from_file(&entry.path()) {
total += user_data.transactions.len();
total += user_data.orders.len();
}
}
}
}
}
total
}
}
pub struct MarketplaceStats {
pub total_products: usize,
pub total_services: usize,
pub total_apps: usize,
pub total_nodes: usize,
pub active_providers: usize,
pub total_transactions: usize,
}
pub struct TestEnvironment {
pub users: Vec<UserPersistentData>,
pub products: Vec<Product>,
pub orders: Vec<Order>,
}
Usage Pattern:
// ✅ Real marketplace data from persistent storage
let marketplace_products = DataAggregator::get_marketplace_products();
let marketplace_services = DataAggregator::get_marketplace_services();
let marketplace_apps = DataAggregator::get_marketplace_apps();
let marketplace_nodes = DataAggregator::get_marketplace_nodes();
let marketplace_stats = DataAggregator::get_marketplace_stats();
🏭 Industry Standard Benefits:
- Real Data Only: No mock data, all products/services come from actual users in
user_data/
- Dynamic Marketplace: Marketplace content grows organically as users add services, apps, nodes
- Authentic Experience: Users see real offerings from real providers
- Production Ready: No mock-to-real data migration needed
- Scalable Architecture: Handles any number of users and their offerings
- Data Integrity: All marketplace data backed by persistent user storage
Priority 5: ErrorBuilder (~120 line reduction)
Solution: Centralized Error Handling
pub struct ErrorBuilder;
impl ErrorBuilder {
pub fn service_error(service: &str, operation: &str, details: &str) -> String {
format!("Failed to {} in {}: {}", operation, service, details)
}
pub fn validation_error(field: &str, reason: &str) -> String {
format!("Validation failed for {}: {}", field, reason)
}
pub fn authentication_error(reason: &str) -> String {
format!("Authentication failed: {}", reason)
}
pub fn not_found_error(resource: &str, id: &str) -> String {
format!("{} with ID '{}' not found", resource, id)
}
}
Priority 6: ContextBuilder Enhancement (~80 line reduction)
Solution: Enhanced Template Context Building
pub struct ContextBuilder {
context: tera::Context,
}
impl ContextBuilder {
pub fn new() -> Self {
Self {
context: tera::Context::new(),
}
}
pub fn with_user(mut self, user: &User) -> Self {
self.context.insert("user", user);
self
}
pub fn with_dashboard_data(mut self, section: &str) -> Self {
self.context.insert("active_page", "dashboard");
self.context.insert("active_section", section);
self
}
pub fn with_marketplace_data(mut self, products: &[Product]) -> Self {
self.context.insert("products", products);
self.context.insert("active_page", "marketplace");
self
}
pub fn build(self) -> tera::Context {
self.context
}
}
📈 Implementation Timeline
Phase 2A: Foundation (Week 1-2)
- ConfigurationBuilder implementation
- ServiceFactory implementation
- ResponseBuilder implementation
- Core infrastructure and testing
Phase 2B: Enhancement (Week 3-4)
- MockDataBuilder implementation
- ErrorBuilder implementation
- ContextBuilder enhancement
- Integration testing and validation
Phase 2C: Migration (Week 5-6)
- Systematic migration of all instances
- Comprehensive testing
- Performance optimization
- Documentation updates
Phase 2D: Validation (Week 7-8)
- Full regression testing
- Performance benchmarking
- Code review and refinement
- Final documentation
🎯 Success Metrics & Validation
Quantitative Metrics
- Lines of Code Reduction: Target 630+ lines (70% of identified opportunities)
- Compilation Time: Maintain or improve current build times
- Test Coverage: Maintain 100% test coverage for all builders
- Performance: No regression in API response times
Qualitative Metrics
- Developer Experience: Reduced boilerplate, clearer patterns
- Maintainability: Single-source-of-truth for all patterns
- Consistency: Uniform patterns across entire codebase
- Documentation: Comprehensive guides for all builders
Validation Checklist
- All existing functionality preserved
- Zero compilation errors
- All tests passing
- Performance benchmarks met
- Code review approved
- Documentation complete
🚀 Advanced Optimization Opportunities
Phase 3: Advanced Patterns (Future Consideration)
-
Macro-Based Builder Generation
#[derive(Builder)] #[builder(pattern = "owned")] struct AutoGeneratedBuilder { // Automatic builder generation }
-
Compile-Time Validation
const_assert!(builder_fields_complete());
-
Builder Trait Abstractions
trait BuilderPattern<T> { fn build(self) -> Result<T, String>; }
-
Performance Optimizations
- Zero-cost abstractions
- Compile-time optimizations
- Memory pool allocations
📚 Implementation Guidelines
For AI Assistance
When implementing Phase 2 consolidation:
- Always use existing patterns from Phase 1 as templates
- Maintain backward compatibility during migration
- Follow single-source-of-truth principle for all builders
- Use
..Default::default()
for extensibility - Include comprehensive error handling in all builders
- Add builder methods to existing structs via
impl
blocks - Create template methods for common use cases
- Validate all builders with comprehensive tests
Migration Strategy
- Implement builder in
src/models/builders.rs
- Add convenience methods to existing structs
- Create migration script to identify all instances
- Replace instances systematically one module at a time
- Test thoroughly after each module migration
- Update documentation for each completed builder
🎉 Expected Final State
After Phase 2 completion, the Project Mycelium will achieve:
Code Quality
- ~1,430 total lines eliminated (Phase 1 + Phase 2)
- Industry-standard builder patterns throughout entire codebase
- Single-source-of-truth architecture for all major patterns
- Zero code duplication in construction patterns
Developer Experience
- Consistent patterns across all modules
- Self-documenting code through builder methods
- Reduced cognitive load for new developers
- Enhanced maintainability for future features
Architecture Benefits
- Future-proof foundation for continued development
- Easy extensibility for new features
- Simplified testing through mock builders
- Performance optimizations through centralized patterns
This roadmap provides a clear path to achieving maximum builder pattern consolidation while maintaining all existing features and dramatically improving code quality and maintainability.