79 lines
4.2 KiB
Text
79 lines
4.2 KiB
Text
|
|
let d = import "adr-defaults.ncl" in
|
||
|
|
|
||
|
|
d.make_adr {
|
||
|
|
id = "adr-003",
|
||
|
|
title = "Axum as the Sole HTTP Framework for vapora-backend",
|
||
|
|
status = 'Accepted,
|
||
|
|
date = "2024-11-01",
|
||
|
|
|
||
|
|
context = "vapora-backend exposes 40+ REST endpoints plus WebSocket connections for real-time updates. The framework choice determines the async model, middleware composition, and extractor ergonomics for the entire API surface. As of 2026-03-27, axum 0.8.8 is in use (the markdown ADR references 0.8.6, which is stale). The Tower ecosystem is the chosen middleware stack.",
|
||
|
|
|
||
|
|
decision = "Axum is the only HTTP framework allowed in vapora-backend. No Actix-Web, Rocket, or Warp. All HTTP handlers use Axum extractors and Router composition. Middleware uses the Tower layer model. WebSocket upgrades use axum::extract::ws.",
|
||
|
|
|
||
|
|
rationale = [
|
||
|
|
{
|
||
|
|
claim = "Axum is Tokio-native with zero abstraction over the async runtime",
|
||
|
|
detail = "vapora runs a Tokio multi-threaded runtime. Axum's Handler trait is directly implemented over Tokio futures — no bridging layer, no actor model overhead. This eliminates the async runtime impedance mismatch that Actix-Web introduces.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
claim = "Tower middleware composes predictably with all Axum routes",
|
||
|
|
detail = "CorsLayer, TraceLayer, CompressionLayer, and authentication middleware are composed via ServiceBuilder, not framework-specific macros. This means middleware ordering is explicit, testable, and portable to other Tower-based services.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
claim = "Type-safe extractors eliminate runtime deserialization panics",
|
||
|
|
detail = "Json<T>, Path<T>, State<T> extractors fail at compile time if the handler signature doesn't match — not at runtime. This catches API contract violations before deployment.",
|
||
|
|
},
|
||
|
|
],
|
||
|
|
|
||
|
|
consequences = {
|
||
|
|
positive = [
|
||
|
|
"All API surface is tested via axum-test's TestClient without a real TCP socket",
|
||
|
|
"Tower middleware applies uniformly to all routes, including WebSocket upgrade paths",
|
||
|
|
"Adding new endpoints requires only adding a handler fn and a route entry — no boilerplate registration",
|
||
|
|
"IntoResponse impl on VaporaError provides consistent error serialization across all handlers",
|
||
|
|
],
|
||
|
|
negative = [
|
||
|
|
"Axum's 0.8.x API introduced breaking changes from 0.7 (extractor signatures, Router typing) — upgrades require wholesale migration",
|
||
|
|
"Axum lacks built-in request body size limiting — must be added via RequestBodyLimitLayer",
|
||
|
|
],
|
||
|
|
},
|
||
|
|
|
||
|
|
alternatives_considered = [
|
||
|
|
{
|
||
|
|
option = "Actix-Web",
|
||
|
|
why_rejected = "Actor model adds coordination overhead not needed for stateless API handlers. Different async patterns from Tokio primitives make integration with NATS JetStream and SurrealDB clients awkward.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
option = "Rocket",
|
||
|
|
why_rejected = "Synchronous-first design. async support was added as an afterthought, leading to executor boundary issues in Tokio-native code.",
|
||
|
|
},
|
||
|
|
],
|
||
|
|
|
||
|
|
constraints = [
|
||
|
|
{
|
||
|
|
id = "axum-only-http-framework",
|
||
|
|
claim = "vapora-backend must not import actix-web, rocket, or warp",
|
||
|
|
scope = "vapora-backend",
|
||
|
|
severity = 'Hard,
|
||
|
|
check = { tag = 'Cargo, crate = "vapora-backend", forbidden_deps = ["actix-web", "rocket", "warp"] },
|
||
|
|
rationale = "Multiple HTTP frameworks in one binary create conflicting async executor registrations, duplicated middleware chains, and inconsistent error serialization.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id = "all-handlers-via-axum-router",
|
||
|
|
claim = "All HTTP endpoints must be registered via axum::Router — no raw hyper service registration",
|
||
|
|
scope = "vapora-backend/src/main.rs, vapora-backend/src/api/",
|
||
|
|
severity = 'Hard,
|
||
|
|
check = { tag = 'Grep, pattern = "axum::Router", paths = ["crates/vapora-backend/src/main.rs"], must_be_empty = false },
|
||
|
|
rationale = "Bypassing the Axum router skips middleware layers (tracing, CORS, auth) applied at the Router level.",
|
||
|
|
},
|
||
|
|
],
|
||
|
|
|
||
|
|
related_adrs = ["adr-002"],
|
||
|
|
|
||
|
|
ontology_check = {
|
||
|
|
decision_string = "axum 0.8.x is the sole HTTP framework in vapora-backend; Tower middleware stack; no actix-web/rocket/warp",
|
||
|
|
invariants_at_risk = [],
|
||
|
|
verdict = 'Safe,
|
||
|
|
},
|
||
|
|
}
|