this

πŸš€ ServerBuilder - Auto-Registration of Routes

🎯 Objective Achieved

The framework now automatically handles ALL routes!

Users simply declare a module, and all CRUD and link routes are generated automatically.


πŸ“Š Before vs After

❌ Before (340 lines in main.rs)

// main.rs - 340 lines of boilerplate!
let app = Router::new()
    .route("/orders", get(list_orders).post(create_order))
    .route("/orders/:id", get(get_order).put(update_order).delete(delete_order))
    .with_state(order_state)
    .route("/invoices", get(list_invoices).post(create_invoice))
    .route("/invoices/:id", get(get_invoice).put(update_invoice).delete(delete_invoice))
    .with_state(invoice_state)
    .route("/payments", get(list_payments).post(create_payment))
    .route("/payments/:id", get(get_payment).put(update_payment).delete(delete_payment))
    .with_state(payment_state)
    .route("/:entity_type/:entity_id/:route_name", get(list_links))
    // ... 200+ lines of routes and handlers

βœ… After (40 total lines, including 20 lines of routing code)

// main.rs - EVERYTHING is auto-generated!
#[tokio::main]
async fn main() -> Result<()> {
    let entity_store = EntityStore::new();
    populate_test_data(&entity_store)?;

    let module = BillingModule::new(entity_store);

    let app = ServerBuilder::new()
        .with_link_service(InMemoryLinkService::new())
        .register_module(module)?  // ← Everything happens here!
        .build()?;

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await?;
    axum::serve(listener, app).await?;
    Ok(())
}

Reduction: -88% code! (340 β†’ 40 lines)


πŸ—οΈ Implemented Architecture

New Module: src/server/

src/server/
β”œβ”€β”€ mod.rs              - Module exports
β”œβ”€β”€ builder.rs          - ServerBuilder (fluent API)
β”œβ”€β”€ entity_registry.rs  - Entity registry
└── router.rs           - Link route generation

Key Components

1. ServerBuilder (src/server/builder.rs)

pub struct ServerBuilder {
    link_service: Option<Arc<dyn LinkService>>,
    entity_registry: EntityRegistry,
    modules: Vec<Arc<dyn Module>>,
}

impl ServerBuilder {
    pub fn new() -> Self { /* ... */ }
    
    pub fn with_link_service(mut self, service: impl LinkService + 'static) -> Self {
        self.link_service = Some(Arc::new(service));
        self
    }
    
    pub fn register_module(mut self, module: impl Module + 'static) -> Result<Self> {
        let module = Arc::new(module);
        
        // 1. Register entities from module
        module.register_entities(&mut self.entity_registry);
        
        // 2. Store module reference
        self.modules.push(module);
        
        Ok(self)
    }
    
    pub fn build(mut self) -> Result<Router> {
        // 3. Merge all YAML configurations
        let config = self.merge_configs()?;
        
        // 4. Build entity fetchers map
        let mut fetchers_map: HashMap<String, Arc<dyn EntityFetcher>> = HashMap::new();
        for module in &self.modules {
            for entity_type in module.entity_types() {
                if let Some(fetcher) = module.get_entity_fetcher(entity_type) {
                    fetchers_map.insert(entity_type.to_string(), fetcher);
                }
            }
        }
        
        // 5. Build entity creators map
        let mut creators_map: HashMap<String, Arc<dyn EntityCreator>> = HashMap::new();
        for module in &self.modules {
            for entity_type in module.entity_types() {
                if let Some(creator) = module.get_entity_creator(entity_type) {
                    creators_map.insert(entity_type.to_string(), creator);
                }
            }
        }
        
        // 6. Create link app state
        let link_state = AppState {
            link_service,
            config,
            registry,
            entity_fetchers: Arc::new(fetchers_map),
            entity_creators: Arc::new(creators_map),
        };
        
        // 7. Auto-generate ALL routes
        let entity_routes = self.entity_registry.build_routes();
        let link_routes = build_link_routes(link_state);
        
        Ok(entity_routes.merge(link_routes))
    }
}

2. EntityRegistry (src/server/entity_registry.rs)

pub trait EntityDescriptor: Send + Sync {
    fn entity_type(&self) -> &str;
    fn plural(&self) -> &str;
    fn build_routes(&self) -> Router;  // Each entity provides its routes
}

pub struct EntityRegistry {
    descriptors: Vec<Box<dyn EntityDescriptor>>,
}

impl EntityRegistry {
    pub fn register(&mut self, descriptor: Box<dyn EntityDescriptor>) {
        self.descriptors.push(descriptor);
    }
    
    pub fn build_routes(&self) -> Router {
        let mut router = Router::new();
        for descriptor in &self.descriptors {
            router = router.merge(descriptor.build_routes());
        }
        router
    }
}

3. EntityDescriptor (per entity)

// examples/microservice/entities/order/descriptor.rs
pub struct OrderDescriptor {
    pub store: OrderStore,
}

impl EntityDescriptor for OrderDescriptor {
    fn entity_type(&self) -> &str { "order" }
    fn plural(&self) -> &str { "orders" }
    
    fn build_routes(&self) -> Router {
        let state = OrderAppState { store: self.store.clone() };
        Router::new()
            .route("/orders", get(list_orders).post(create_order))
            .route("/orders/{id}", 
                get(get_order)
                .put(update_order)
                .delete(delete_order))
            .with_state(state)
    }
}

4. Extended Module Trait (src/core/module.rs)

pub trait Module: Send + Sync {
    fn name(&self) -> &str;
    fn version(&self) -> &str;
    fn entity_types(&self) -> Vec<&str>;
    fn links_config(&self) -> Result<LinksConfig>;
    
    // Register entity descriptors
    fn register_entities(&self, registry: &mut EntityRegistry);
    
    // Provide entity fetchers for link enrichment
    fn get_entity_fetcher(&self, entity_type: &str) -> Option<Arc<dyn EntityFetcher>>;
    
    // πŸ†• Provide entity creators for auto-creation with linking
    fn get_entity_creator(&self, entity_type: &str) -> Option<Arc<dyn EntityCreator>>;
}

πŸ“ Entity Structure

Each entity now has a descriptor.rs:

entities/
β”œβ”€β”€ order/
β”‚   β”œβ”€β”€ descriptor.rs   # πŸ†• Auto-registration of routes
β”‚   β”œβ”€β”€ model.rs        # Order struct (uses macro!)
β”‚   β”œβ”€β”€ store.rs        # OrderStore
β”‚   └── handlers.rs     # CRUD handlers
β”œβ”€β”€ invoice/
β”‚   β”œβ”€β”€ descriptor.rs   # πŸ†•
β”‚   └── ...
└── payment/
    β”œβ”€β”€ descriptor.rs   # πŸ†•
    └── ...

πŸ”„ Execution Flow

1. main.rs
   └─> BillingModule::new(store)
   
2. ServerBuilder::new()
   └─> .with_link_service(InMemoryLinkService::new())
   └─> .register_module(module)
       β”œβ”€> module.register_entities(&mut registry)
       β”‚   β”œβ”€> registry.register(OrderDescriptor)
       β”‚   β”œβ”€> registry.register(InvoiceDescriptor)
       β”‚   └─> registry.register(PaymentDescriptor)
       β”‚
       β”œβ”€> Store module reference
       └─> Collect module configuration
   
3. .build()
   β”œβ”€> Merge all YAML configs
   β”‚
   β”œβ”€> Build entity_fetchers map
   β”‚   β”œβ”€> module.get_entity_fetcher("order")
   β”‚   β”œβ”€> module.get_entity_fetcher("invoice")
   β”‚   └─> module.get_entity_fetcher("payment")
   β”‚
   β”œβ”€> Build entity_creators map
   β”‚   β”œβ”€> module.get_entity_creator("order")
   β”‚   β”œβ”€> module.get_entity_creator("invoice")
   β”‚   └─> module.get_entity_creator("payment")
   β”‚
   β”œβ”€> entity_registry.build_routes()
   β”‚   β”œβ”€> OrderDescriptor.build_routes()    β†’ /orders, /orders/{id}
   β”‚   β”œβ”€> InvoiceDescriptor.build_routes()  β†’ /invoices, /invoices/{id}
   β”‚   └─> PaymentDescriptor.build_routes()  β†’ /payments, /payments/{id}
   β”‚
   └─> build_link_routes()
       β”œβ”€> /{entity}/{id}/{route_name}
       β”œβ”€> /{source}/{id}/{route_name}/{target_id}
       └─> /{entity}/{id}/links
   
4. Final Router with ALL routes auto-generated!

✨ Auto-Generated Routes

CRUD Routes (per entity)

GET    /orders           β†’ list_orders
POST   /orders           β†’ create_order
GET    /orders/{id}      β†’ get_order
PUT    /orders/{id}      β†’ update_order
DELETE /orders/{id}      β†’ delete_order

GET    /invoices         β†’ list_invoices
POST   /invoices         β†’ create_invoice
GET    /invoices/{id}    β†’ get_invoice
PUT    /invoices/{id}    β†’ update_invoice
DELETE /invoices/{id}    β†’ delete_invoice

GET    /payments         β†’ list_payments
POST   /payments         β†’ create_payment
GET    /payments/{id}    β†’ get_payment
PUT    /payments/{id}    β†’ update_payment
DELETE /payments/{id}    β†’ delete_payment
GET    /{entity_type}/{entity_id}/{route_name}
       β†’ List links (e.g., /orders/123/invoices)
       β†’ Returns enriched links with target entities

POST   /{entity_type}/{entity_id}/{route_name}
       β†’ Create new entity + link automatically
       β†’ Body: { "entity": {...}, "metadata": {...} }
       β†’ Returns: { "entity": {...}, "link": {...} }

GET    /{source_type}/{source_id}/{route_name}/{target_id}
       β†’ Get specific link (e.g., /orders/123/invoices/456)
       β†’ Returns enriched link with both entities

POST   /{source_type}/{source_id}/{route_name}/{target_id}
       β†’ Create link between existing entities
       β†’ Body: { "metadata": {...} }

PUT    /{source_type}/{source_id}/{route_name}/{target_id}
       β†’ Update link metadata (e.g., /orders/123/invoices/456)

DELETE /{source_type}/{source_id}/{route_name}/{target_id}
       β†’ Delete link (e.g., /orders/123/invoices/456)

GET    /{entity_type}/{entity_id}/links
       β†’ List available link types (introspection)

Note: The route_name (e.g., "invoices") is automatically resolved to the
      technical link_type (e.g., "has_invoice") by LinkRouteRegistry.

🎁 Benefits

1. Zero Boilerplate

Before:

After:

2. Guaranteed Consistency

All entities automatically have:

Impossible to forget a route!

3. Infinite Scalability

// Add 10 new entities
impl Module for MyModule {
    fn register_entities(&self, registry: &mut EntityRegistry) {
        registry.register(Box::new(ProductDescriptor::new(store.products.clone())));
        registry.register(Box::new(CustomerDescriptor::new(store.customers.clone())));
        registry.register(Box::new(SupplierDescriptor::new(store.suppliers.clone())));
        // ... 7 others
    }
}

// All routes are auto-generated!
// Not a single line of manual routing to write

4. Simplified Maintenance

Modify CRUD route patterns?

5. Auto-Generated Documentation

println!("πŸ“š All routes auto-generated:");
println!("  - GET    /orders, /invoices, /payments");
println!("  - POST   /orders, /invoices, /payments");
// ...

πŸ§ͺ Tests

Compilation

$ cargo build --example microservice
    Finished `dev` profile in 1.44s
βœ… Compilation successful

Functionality

$ cargo run --example microservice &
πŸš€ Starting billing-service v1.0.0
πŸ“¦ Entities: ["order", "invoice", "payment"]
🌐 Server running on http://127.0.0.1:3000

$ curl http://localhost:3000/orders | jq '.count'
2

$ curl http://localhost:3000/invoices | jq '.count'
3

$ curl -X POST http://localhost:3000/orders \
  -d '{"number":"ORD-AUTO","amount":999.99,"status":"active"}' | jq '.number'
"ORD-AUTO"

βœ… All routes working!

πŸ“ Migration Guide

To Adapt an Existing Entity

  1. Create descriptor.rs
// entities/my_entity/descriptor.rs
use super::{handlers::*, store::MyEntityStore};
use axum::{routing::get, Router};
use this::prelude::EntityDescriptor;

pub struct MyEntityDescriptor {
    pub store: MyEntityStore,
}

impl MyEntityDescriptor {
    pub fn new(store: MyEntityStore) -> Self {
        Self { store }
    }
}

impl EntityDescriptor for MyEntityDescriptor {
    fn entity_type(&self) -> &str { "my_entity" }
    fn plural(&self) -> &str { "my_entities" }
    
    fn build_routes(&self) -> Router {
        let state = MyEntityAppState { store: self.store.clone() };
        Router::new()
            .route("/my_entities", get(list_my_entities).post(create_my_entity))
            .route("/my_entities/{id}", 
                get(get_my_entity)
                .put(update_my_entity)
                .delete(delete_my_entity))
            .with_state(state)
    }
}
  1. Register in Module
impl Module for MyModule {
    fn register_entities(&self, registry: &mut EntityRegistry) {
        registry.register(Box::new(
            MyEntityDescriptor::new(self.store.my_entities.clone())
        ));
    }
    
    fn get_entity_fetcher(&self, entity_type: &str) -> Option<Arc<dyn EntityFetcher>> {
        match entity_type {
            "my_entity" => Some(Arc::new(self.store.my_entities.clone())),
            _ => None,
        }
    }
    
    fn get_entity_creator(&self, entity_type: &str) -> Option<Arc<dyn EntityCreator>> {
        match entity_type {
            "my_entity" => Some(Arc::new(self.store.my_entities.clone())),
            _ => None,
        }
    }
}
  1. Use ServerBuilder in main.rs
let app = ServerBuilder::new()
    .with_link_service(InMemoryLinkService::new())
    .register_module(module)?
    .build()?;

🎯 Vision Realized

Initial Objective

β€œAdding a new entity should NEVER require modifying existing module code.”

βœ… Result

To add a new entity:

  1. Create model.rs (data structure using macro)
  2. Create store.rs (persistence + EntityFetcher + EntityCreator)
  3. Create handlers.rs (CRUD logic)
  4. Create descriptor.rs (auto-registration)
  5. Register in register_entities()

ZERO modification to routing code in main.rs!


πŸŽ‰ Conclusion

The ServerBuilder implementation provides:

βœ… -88% code in main.rs (340 β†’ 40 lines)
βœ… Zero boilerplate for routing
βœ… Auto-generation of all routes
βœ… Guaranteed consistency between entities
βœ… Infinite scalability (3 or 300 entities = same simplicity)
βœ… Maximum maintainability (1 place to modify patterns)
βœ… EntityCreator integration for automatic entity creation with linking
βœ… Link enrichment with EntityFetcher for optimal performance

This is exactly the framework’s vision: declare modules, and everything else is automatic! πŸš€πŸ¦€βœ¨


πŸ“š Files Created/Modified

New Files (Core)

New Files (Example)

Modified Files

Total: 11 files created/modified for a production-ready architecture!