← Volver al listado de tecnologías

Capitulo 4: Setup del Cargo Workspace

Por: SiempreListo
rustwasmaxumleptos

Que es un Cargo Workspace

Un Cargo Workspace permite gestionar multiples crates (proyectos) en un solo repositorio. Comparten un unico Cargo.lock y directorio target/, lo que garantiza versiones consistentes y compilaciones mas rapidas por cache compartido.

Para nuestro Kanban, el workspace agrupa backend y frontend en un mismo repositorio pero como binarios independientes.

Cargo.toml raiz

El archivo raiz solo define los miembros del workspace:

[workspace]
resolver = "2"
members = [
    "apps/app-backend",
    "apps/app-frontend",
]

No hay dependencias aqui. Cada crate define las suyas.

Crear la estructura

# Crear el directorio raiz
mkdir rust-poc && cd rust-poc

# Crear los proyectos
mkdir -p apps
cargo new apps/app-backend
cargo new apps/app-frontend

# Crear directorio de migraciones
mkdir -p apps/app-bbdd/migrations

El directorio app-bbdd no es un crate de Rust: es simplemente una carpeta con archivos SQL y la base de datos SQLite.

Dependencias del Backend

apps/app-backend/Cargo.toml

[package]
name = "app-backend"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }
tower-http = { version = "0.6", features = ["cors"] }
thiserror = "2"
anyhow = "1"
uuid = { version = "1", features = ["v4", "serde"] }
tracing = "0.1"
tracing-subscriber = "0.3"
dotenvy = "0.15"
chrono = { version = "0.4", features = ["serde"] }

[dev-dependencies]
mockall = "0.13"
tower = { version = "0.5", features = ["util"] }
http-body-util = "0.1"

Explicacion de cada dependencia

Framework y runtime:

CrateVersionPara que sirve
axum0.8Framework HTTP. Define rutas, extractors y handlers
tokio1 (full)Runtime async. full habilita IO, timers, macros, multithreading
tower-http0.6 (cors)Middleware HTTP. Usamos CorsLayer::permissive() para permitir requests del frontend WASM

Serializacion:

CrateVersionPara que sirve
serde1 (derive)Serializar/deserializar structs. derive habilita #[derive(Serialize, Deserialize)]
serde_json1Convertir entre structs Rust y JSON

Base de datos:

CrateVersionPara que sirve
sqlx0.8Queries SQL async. runtime-tokio integra con nuestro runtime, sqlite habilita el driver SQLite

Manejo de errores:

CrateVersionPara que sirve
thiserror2Macro #[derive(Error)] para crear enums de error con mensajes. Usado en DomainError
anyhow1Error generico para main() y situaciones donde no necesitas un tipo especifico

Utilidades:

CrateVersionPara que sirve
uuid1 (v4, serde)Genera UUIDs v4 para CardId. serde permite serializar/deserializar UUIDs
chrono0.4 (serde)Manejo de fechas (DateTime<Utc>). serde para serializar timestamps
tracing0.1Logging estructurado. tracing::info!("Servidor en http://localhost:{port}")
tracing-subscriber0.3Configura la salida de logs (formato, nivel)
dotenvy0.15Carga variables de entorno desde .env

Dev dependencies (solo para tests):

CrateVersionPara que sirve
mockall0.13Genera mocks automaticos desde traits con #[cfg_attr(test, mockall::automock)]
tower0.5 (util)Utilidades para testear servicios Tower/Axum
http-body-util0.1Helpers para leer bodies HTTP en tests

Dependencias del Frontend

apps/app-frontend/Cargo.toml

[package]
name = "app-frontend"
version = "0.1.0"
edition = "2021"

[dependencies]
leptos = { version = "0.7", features = ["csr"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
uuid = { version = "1", features = ["v4", "serde", "js"] }
gloo-net = { version = "0.6", features = ["http"] }
web-sys = { version = "0.3", features = [
    "DragEvent",
    "DataTransfer",
    "HtmlElement",
    "Element",
] }
wasm-bindgen = "0.2"

Explicacion de cada dependencia

CrateVersionPara que sirve
leptos0.7 (csr)Framework UI reactivo. csr = Client-Side Rendering, compila a WASM sin servidor
serde1 (derive)Deserializar las respuestas JSON del backend a structs Rust
serde_json1Construir bodies JSON para enviar al backend
uuid1 (v4, serde, js)UUIDs en el frontend. js usa crypto.getRandomValues() del navegador en vez de /dev/urandom
gloo-net0.6 (http)Cliente HTTP para WASM. Wrapper sobre la Fetch API del navegador
web-sys0.3Bindings a las Web APIs del navegador. Cada feature habilita una API especifica
wasm-bindgen0.2Puente entre Rust/WASM y JavaScript. Requerido por web-sys y gloo

Features de web-sys detalladas

web-sys es modular: solo incluyes las APIs que necesitas. Para el drag & drop:

Archivo .env

DATABASE_URL=sqlite:apps/app-bbdd/kanban.db?mode=rwc
PORT=22000

Migracion SQL

apps/app-bbdd/migrations/001_create_cards.sql

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
);

La migracion se ejecuta al iniciar el backend con include_str!:

sqlx::query(include_str!("../../app-bbdd/migrations/001_create_cards.sql"))
    .execute(&pool)
    .await?;

include_str! embebe el contenido del archivo SQL en el binario en tiempo de compilacion. CREATE TABLE IF NOT EXISTS es idempotente: se puede ejecutar multiples veces sin error.

Compilacion WASM con Trunk

Para compilar el frontend a WASM necesitas Trunk, un bundler para aplicaciones Rust/WASM:

# Instalar Trunk
cargo install trunk

# Agregar el target WASM
rustup target add wasm32-unknown-unknown

Trunk busca un archivo index.html en la raiz del crate frontend:

<!-- apps/app-frontend/index.html -->
<!DOCTYPE html>
<html>
<head>
    <link data-trunk rel="css" href="style.css" />
</head>
<body></body>
</html>

Trunk automaticamente detecta que es un proyecto Rust, compila a WASM, genera los archivos JS de pegamento (glue code), e inyecta los scripts en el HTML.

Verificar que todo compila

# Compilar todo el workspace (backend nativo)
cargo build --workspace

# Compilar solo el frontend a WASM
cd apps/app-frontend && trunk build

# Ejecutar el backend
cargo run -p app-backend

# Servir el frontend con hot-reload
cd apps/app-frontend && trunk serve

Si cargo build --workspace compila sin errores, la estructura esta correcta. El backend compila a binario nativo y el frontend se compila por separado a WASM con Trunk.

Resumen

La configuracion del workspace es minimalista:

El Cargo Workspace permite que ambos crates compartan Cargo.lock (versiones consistentes) y target/ (cache de compilacion), mientras cada uno define sus propias dependencias especificas para su plataforma.


← Anterior: Arquitectura Hexagonal | Siguiente: Dominio - Entidades y Value Objects →