Capitulo 4: Setup del Cargo Workspace
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",
]
resolver = "2": usa el resolver de dependencias moderno de Cargo (requerido desde edition 2021)members: lista de crates que componen el workspace
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:
| Crate | Version | Para que sirve |
|---|---|---|
axum | 0.8 | Framework HTTP. Define rutas, extractors y handlers |
tokio | 1 (full) | Runtime async. full habilita IO, timers, macros, multithreading |
tower-http | 0.6 (cors) | Middleware HTTP. Usamos CorsLayer::permissive() para permitir requests del frontend WASM |
Serializacion:
| Crate | Version | Para que sirve |
|---|---|---|
serde | 1 (derive) | Serializar/deserializar structs. derive habilita #[derive(Serialize, Deserialize)] |
serde_json | 1 | Convertir entre structs Rust y JSON |
Base de datos:
| Crate | Version | Para que sirve |
|---|---|---|
sqlx | 0.8 | Queries SQL async. runtime-tokio integra con nuestro runtime, sqlite habilita el driver SQLite |
Manejo de errores:
| Crate | Version | Para que sirve |
|---|---|---|
thiserror | 2 | Macro #[derive(Error)] para crear enums de error con mensajes. Usado en DomainError |
anyhow | 1 | Error generico para main() y situaciones donde no necesitas un tipo especifico |
Utilidades:
| Crate | Version | Para que sirve |
|---|---|---|
uuid | 1 (v4, serde) | Genera UUIDs v4 para CardId. serde permite serializar/deserializar UUIDs |
chrono | 0.4 (serde) | Manejo de fechas (DateTime<Utc>). serde para serializar timestamps |
tracing | 0.1 | Logging estructurado. tracing::info!("Servidor en http://localhost:{port}") |
tracing-subscriber | 0.3 | Configura la salida de logs (formato, nivel) |
dotenvy | 0.15 | Carga variables de entorno desde .env |
Dev dependencies (solo para tests):
| Crate | Version | Para que sirve |
|---|---|---|
mockall | 0.13 | Genera mocks automaticos desde traits con #[cfg_attr(test, mockall::automock)] |
tower | 0.5 (util) | Utilidades para testear servicios Tower/Axum |
http-body-util | 0.1 | Helpers 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
| Crate | Version | Para que sirve |
|---|---|---|
leptos | 0.7 (csr) | Framework UI reactivo. csr = Client-Side Rendering, compila a WASM sin servidor |
serde | 1 (derive) | Deserializar las respuestas JSON del backend a structs Rust |
serde_json | 1 | Construir bodies JSON para enviar al backend |
uuid | 1 (v4, serde, js) | UUIDs en el frontend. js usa crypto.getRandomValues() del navegador en vez de /dev/urandom |
gloo-net | 0.6 (http) | Cliente HTTP para WASM. Wrapper sobre la Fetch API del navegador |
web-sys | 0.3 | Bindings a las Web APIs del navegador. Cada feature habilita una API especifica |
wasm-bindgen | 0.2 | Puente 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:
DragEvent: eventosdragstart,dragover,dropDataTransfer:getData()/setData()para transferir el ID de la tarjetaHtmlElementyElement: manipulacion basica del DOM
Archivo .env
DATABASE_URL=sqlite:apps/app-bbdd/kanban.db?mode=rwc
PORT=22000
DATABASE_URL: ruta relativa al archivo SQLite.?mode=rwcsignifica Read-Write-Create (crea el archivo si no existe)PORT: puerto donde escucha Axum. Elegimos 22000 para no colisionar con puertos comunes
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:
- 1 archivo
Cargo.tomlraiz con la lista de miembros - 2 crates: backend (binario nativo) y frontend (compila a WASM)
- 1 directorio
app-bbddcon migraciones SQL - 1 archivo
.envcon configuracion
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 →