Capitulo 2: Analisis de Frameworks
Introduccion
Elegir el stack correcto en Rust no es trivial. El ecosistema tiene multiples opciones para cada capa. En este capitulo analizamos cada decision y explicamos el por que detras de cada eleccion.
Backend: Axum vs Actix-web vs Rocket
Comparativa
| Criterio | Axum 0.8 | Actix-web 4 | Rocket 0.5 |
|---|---|---|---|
| Runtime async | Tokio (nativo) | Actix runtime | Tokio |
| Extractors | Tipados con FromRequest | Tipados | Macros + guards |
| Middleware | Tower (estandar) | Propio | Fairings |
| Curva de aprendizaje | Media | Media | Baja |
| Ecosistema | Tower + Tokio completo | Propio | Limitado |
| Macros magicas | Minimas | Pocas | Muchas |
Por que Axum
Axum es parte del ecosistema Tokio, el runtime async mas usado de Rust. Esto significa que cualquier libreria que use Tokio (SQLx, reqwest, tonic) funciona sin adaptadores.
Los extractors de Axum son funciones puras que reciben y retornan tipos concretos:
pub async fn create_card(
State(state): State<AppState>, // Inyeccion de estado
Json(dto): Json<CreateCardDto>, // Body parseado automaticamente
) -> Result<(StatusCode, Json<Card>), DomainError> {
// ...
}
Axum no necesita macros #[get("/path")]. Las rutas se definen como datos:
Router::new()
.route("/api/cards", get(card_handler::list_cards))
.route("/api/cards", post(card_handler::create_card))
.route("/api/cards/{id}/move", patch(card_handler::move_card))
.route("/api/cards/{id}", delete(card_handler::delete_card))
El middleware usa Tower, un estandar compartido por todo el ecosistema Tokio. CORS, logging, rate limiting: todo es un Tower layer reutilizable.
Frontend WASM: Leptos vs Yew vs Dioxus vs Sycamore
Comparativa
| Criterio | Leptos 0.7 | Yew 0.21 | Dioxus 0.6 | Sycamore 0.9 |
|---|---|---|---|---|
| Reactividad | Signals (fine-grained) | Virtual DOM | Signals | Signals |
| Sintaxis | RSX macro view!{} | html!{} macro | RSX similar a React | view!{} macro |
| SSR | Si (integrado) | Limitado | Si | Si |
| CSR standalone | Si | Si | Si | Si |
| Rendimiento | Excelente | Bueno | Muy bueno | Excelente |
| Madurez | En crecimiento | Maduro | En crecimiento | Joven |
Por que Leptos
Leptos usa signals reactivos de grano fino: cuando un signal cambia, solo se actualiza la parte exacta del DOM afectada. No hay Virtual DOM ni diffing.
let (cards, set_cards) = signal(Vec::<Card>::new());
// Cuando set_cards.set(nueva_lista) se ejecuta,
// solo los nodos del DOM que leen `cards` se actualizan
La macro view! permite escribir HTML con interpolacion de Rust, similar a JSX pero en compilacion:
view! {
<div class="board">
<Lane title="Todo" lane_id="todo"
cards=cards_by_lane(&all, "todo")
on_drop=on_drop on_delete=on_delete />
</div>
}
Leptos soporta CSR (Client-Side Rendering) y SSR con el mismo codigo. Para esta POC usamos CSR puro (features = ["csr"]), pero migrar a SSR es cambiar un feature flag.
Los componentes se definen con #[component] y reciben props tipados:
#[component]
pub fn CardItem(
card: Card,
on_delete: Callback<String>,
) -> impl IntoView {
// El compilador verifica que pases los props correctos
}
Base de datos: SQLite vs PostgreSQL
Comparativa
| Criterio | SQLite | PostgreSQL |
|---|---|---|
| Setup | Zero config, archivo unico | Servidor, usuario, permisos |
| Concurrencia | WAL mode (lecturas ok) | Excelente |
| Tipo de proyecto | POC, apps locales, mobile | Produccion, multi-usuario |
| Deploy | Copiar un archivo | Servidor dedicado |
| Features SQL | Basicos | Completos (JSONB, arrays, CTE) |
Por que SQLite
Para una POC (Proof of Concept), SQLite es ideal:
- Sin setup: la BD es un archivo (
kanban.db). Se crea automaticamente con?mode=rwc - Portable: clonas el repo y funciona. Sin instalar PostgreSQL, sin crear usuarios
- Suficiente: nuestro Kanban no necesita concurrencia masiva ni features SQL avanzados
La conexion es directa:
let db_url = "sqlite:apps/app-bbdd/kanban.db?mode=rwc";
let pool = SqlitePool::connect(&db_url).await?;
Las migraciones son SQL plano:
CREATE TABLE IF NOT EXISTS cards (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
lane TEXT NOT NULL DEFAULT 'todo',
position INTEGER NOT NULL DEFAULT 0,
completed BOOLEAN NOT NULL DEFAULT FALSE,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
ORM/Query Builder: SQLx vs Diesel vs SeaORM
Comparativa
| Criterio | SQLx 0.8 | Diesel 2 | SeaORM 1 |
|---|---|---|---|
| Paradigma | Raw SQL + macros | DSL query builder | Active Record |
| Async | Nativo (Tokio) | Sync (async via extension) | Nativo |
| Verificacion | Compile-time (opcional) | Compile-time (schema.rs) | Runtime |
| Migraciones | SQL plano | CLI + generacion | CLI + generacion |
| Curva | Baja (si sabes SQL) | Alta (DSL propio) | Media |
Por que SQLx
SQLx permite escribir SQL puro y verificarlo en tiempo de compilacion. No necesitas aprender un DSL nuevo: si sabes SQL, sabes usar SQLx.
sqlx::query("SELECT * FROM cards ORDER BY lane, position")
.fetch_all(&self.pool)
.await
.map_err(map_err)?;
Es 100% async sobre Tokio, lo cual encaja perfecto con Axum. Y al no generar un schema como Diesel, la configuracion es minima.
HTTP Client (WASM): gloo-net vs reqwest-wasm
Comparativa
| Criterio | gloo-net 0.6 | reqwest (wasm) |
|---|---|---|
| Disenado para | WASM exclusivamente | Multiplataforma |
| Base | Web Fetch API nativa | Abstraccion propia |
| Tamano del bundle | Pequeno | Mayor |
| API | Simple y directa | Rica en features |
| Cookies/auth | Via Fetch API | Integrado |
Por que gloo-net
gloo-net es un wrapper delgado sobre la Fetch API del navegador. Al ejecutarse en WASM, usar la API nativa del navegador es lo mas eficiente:
use gloo_net::http::Request;
Request::get(&format!("{BASE_URL}/cards"))
.send()
.await
.map_err(|e| e.to_string())?
.json::<Vec<Card>>()
.await
.map_err(|e| e.to_string())
reqwest es excelente para Rust nativo, pero en WASM agrega una capa de abstraccion innecesaria. gloo-net es mas liviano y directo.
Resumen de decisiones
| Decision | Elegido | Razon principal |
|---|---|---|
| Backend | Axum | Ecosistema Tokio, extractors tipados |
| Frontend | Leptos | Signals reactivos, RSX, rendimiento |
| BD | SQLite | Zero config, ideal para POC |
| Queries | SQLx | Async nativo, SQL puro, compile-time check |
| HTTP WASM | gloo-net | Wrapper nativo sobre Fetch API |
Cada eleccion prioriza simplicidad y coherencia dentro del ecosistema Rust. No hay la “mejor” opcion universal, sino la mejor para este proyecto especifico.
← Anterior: Introduccion | Siguiente: Arquitectura Hexagonal →