← Volver al listado de tecnologías

Capitulo 2: Analisis de Frameworks

Por: SiempreListo
rustwasmaxumleptos

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

CriterioAxum 0.8Actix-web 4Rocket 0.5
Runtime asyncTokio (nativo)Actix runtimeTokio
ExtractorsTipados con FromRequestTipadosMacros + guards
MiddlewareTower (estandar)PropioFairings
Curva de aprendizajeMediaMediaBaja
EcosistemaTower + Tokio completoPropioLimitado
Macros magicasMinimasPocasMuchas

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

CriterioLeptos 0.7Yew 0.21Dioxus 0.6Sycamore 0.9
ReactividadSignals (fine-grained)Virtual DOMSignalsSignals
SintaxisRSX macro view!{}html!{} macroRSX similar a Reactview!{} macro
SSRSi (integrado)LimitadoSiSi
CSR standaloneSiSiSiSi
RendimientoExcelenteBuenoMuy buenoExcelente
MadurezEn crecimientoMaduroEn crecimientoJoven

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

CriterioSQLitePostgreSQL
SetupZero config, archivo unicoServidor, usuario, permisos
ConcurrenciaWAL mode (lecturas ok)Excelente
Tipo de proyectoPOC, apps locales, mobileProduccion, multi-usuario
DeployCopiar un archivoServidor dedicado
Features SQLBasicosCompletos (JSONB, arrays, CTE)

Por que SQLite

Para una POC (Proof of Concept), SQLite es ideal:

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

CriterioSQLx 0.8Diesel 2SeaORM 1
ParadigmaRaw SQL + macrosDSL query builderActive Record
AsyncNativo (Tokio)Sync (async via extension)Nativo
VerificacionCompile-time (opcional)Compile-time (schema.rs)Runtime
MigracionesSQL planoCLI + generacionCLI + generacion
CurvaBaja (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

Criteriogloo-net 0.6reqwest (wasm)
Disenado paraWASM exclusivamenteMultiplataforma
BaseWeb Fetch API nativaAbstraccion propia
Tamano del bundlePequenoMayor
APISimple y directaRica en features
Cookies/authVia Fetch APIIntegrado

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

DecisionElegidoRazon principal
BackendAxumEcosistema Tokio, extractors tipados
FrontendLeptosSignals reactivos, RSX, rendimiento
BDSQLiteZero config, ideal para POC
QueriesSQLxAsync nativo, SQL puro, compile-time check
HTTP WASMgloo-netWrapper 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 →