ADR-002: Axum como Backend Framework
Status: Accepted | Implemented Date: 2024-11-01 Deciders: Backend Architecture Team Technical Story: Selecting REST API framework with optimal async/middleware composition for Tokio ecosystem
Decision
Usar Axum 0.8.6 como framework REST API (no Actix-Web, no Rocket) para exponer 40+ endpoints de VAPORA.
Rationale
- Composable Middleware: Tower ecosystem provides first-class composable middleware patterns
- Type-Safe Routing: Router defined as strong types (not string-based paths)
- Tokio Ecosystem: Built directly on Tokio (not abstraction layer), enabling precise async control
- Extractors: Powerful extractor system (
Json,State,Path, custom extractors) reduces boilerplate - Performance: Zero-copy response bodies, streaming support, minimal overhead
Alternatives Considered
❌ Actix-Web
- Mature framework with larger ecosystem
- Cons: Actor model adds complexity, different async patterns than Tokio, harder to integrate with Tokio primitives
❌ Rocket
- Developer-friendly API
- Cons: Synchronous-first (async as afterthought), less composable, worse error handling
✅ Axum (CHOSEN)
- Minimal abstraction over Tokio/Tower
- Pros: Composable, type-safe, Tokio-native, growing ecosystem
Trade-offs
Pros:
- ✅ Composable middleware (Tower trait-based)
- ✅ Type-safe routing with strong types
- ✅ Zero-cost abstractions, excellent performance
- ✅ Perfect integration with Tokio async ecosystem
- ✅ Streaming responses, WebSocket support built-in
Cons:
- ⚠️ Smaller ecosystem than Actix-Web
- ⚠️ Steeper learning curve (requires understanding Tower traits)
- ⚠️ Fewer third-party integrations available
Implementation
Router Definition:
#![allow(unused)] fn main() { let app = Router::new() .route("/api/v1/projects", post(create_project).get(list_projects)) .route("/api/v1/projects/:id", get(get_project).put(update_project)) .route("/metrics", get(metrics_handler)) .layer(TraceLayer::new_for_http()) .layer(CorsLayer::permissive()) .layer(Extension(Arc::new(app_state))); let listener = TcpListener::bind("0.0.0.0:8001").await?; axum::serve(listener, app).await?; }
Key Files:
/crates/vapora-backend/src/main.rs:126-259(router setup)/crates/vapora-backend/src/api/(handlers)/crates/vapora-backend/Cargo.toml(dependencies)
Verification
# Build backend
cargo build -p vapora-backend
# Test API endpoints
cargo test -p vapora-backend -- --nocapture
# Run server and check health
cargo run -p vapora-backend &
curl http://localhost:8001/health
curl http://localhost:8001/metrics
Expected: 40+ endpoints accessible, health check responds 200 OK, metrics endpoint returns Prometheus format
Consequences
- All HTTP handling must use Axum extractors (learning curve for team)
- Request/response types must be serializable (integration with serde)
- Middleware stacking order matters (defensive against bugs)
- Easy to add WebSocket support later (Axum has built-in support)
References
- Axum Documentation
/crates/vapora-backend/src/main.rs(router definition)/crates/vapora-backend/Cargo.toml(Axum dependency)
Related ADRs: ADR-001 (Workspace), ADR-008 (Tokio)