Web Domain
Layer 3: Domain Constraints
Domain Constraints → Design Implications
| Domain Rule |
Design Constraint |
Rust Implication |
| Stateless HTTP |
No request-local globals |
State in extractors |
| Concurrency |
Handle many connections |
Async, Send + Sync |
| Latency SLA |
Fast response |
Efficient ownership |
| Security |
Input validation |
Type-safe extractors |
| Observability |
Request tracing |
tracing + tower layers |
Critical Constraints
Async by Default
RULE: Web handlers must not block
WHY: Block one task = block many requests
RUST: async/await, spawn_blocking for CPU work
State Management
RULE: Shared state must be thread-safe
WHY: Handlers run on any thread
RUST: Arc<T>, Arc<RwLock<T>> for mutable
Request Lifecycle
RULE: Resources live only for request duration
WHY: Memory management, no leaks
RUST: Extractors, proper ownership
Trace Down ↓
From constraints to design (Layer 2):
"Need shared application state"
↓ m07-concurrency: Use Arc for thread-safe sharing
↓ m02-resource: Arc<RwLock<T>> for mutable state
"Need request validation"
↓ m05-type-driven: Validated extractors
↓ m06-error-handling: IntoResponse for errors
"Need middleware stack"
↓ m12-lifecycle: Tower layers
↓ m04-zero-cost: Trait-based composition
Framework Comparison
| Framework |
Style |
Best For |
| axum |
Functional, tower |
Modern APIs |
| actix-web |
Actor-based |
High performance |
| warp |
Filter composition |
Composable APIs |
| rocket |
Macro-driven |
Rapid development |
Key Crates
| Purpose |
Crate |
| HTTP server |
axum, actix-web |
| HTTP client |
reqwest |
| JSON |
serde_json |
| Auth/JWT |
jsonwebtoken |
| Session |
tower-sessions |
| Database |
sqlx, diesel |
| Middleware |
tower |
Design Patterns
| Pattern |
Purpose |
Implementation |
| Extractors |
Request parsing |
State(db), Json(payload) |
| Error response |
Unified errors |
impl IntoResponse |
| Middleware |
Cross-cutting |
Tower layers |
| Shared state |
App config |
Arc<AppState> |
Code Pattern: Axum Handler
async fn handler(
State(db): State<Arc<DbPool>>,
Json(payload): Json<CreateUser>,
) -> Result<Json<User>, AppError> {
let user = db.create_user(&payload).await?;
Ok(Json(user))
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, message) = match self {
Self::NotFound => (StatusCode::NOT_FOUND, "Not found"),
Self::Internal(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal error"),
};
(status, Json(json!({"error": message}))).into_response()
}
}
Common Mistakes
| Mistake |
Domain Violation |
Fix |
| Blocking in handler |
Latency spike |
spawn_blocking |
| Rc in state |
Not Send + Sync |
Use Arc |
| No validation |
Security risk |
Type-safe extractors |
| No error response |
Bad UX |
IntoResponse impl |
Trace to Layer 1
| Constraint |
Layer 2 Pattern |
Layer 1 Implementation |
| Async handlers |
Async/await |
tokio runtime |
| Thread-safe state |
Shared state |
Arc, Arc<RwLock> |
| Request lifecycle |
Extractors |
Ownership via From |
| Middleware |
Tower layers |
Trait-based composition |
Related Skills
| When |
See |
| Async patterns |
m07-concurrency |
| State management |
m02-resource |
| Error handling |
m06-error-handling |
| Middleware design |
m12-lifecycle |