Monorepositorio con Rust: organización del código en la práctica
Un monorepositorio (monorepo) es un enfoque de gestión de código donde múltiples proyectos o componentes se almacenan en un único repositorio. En el ecosistema de Rust, esta práctica es especialmente popular gracias al soporte integrado de Cargo Workspace. En este artículo, analizaremos cómo organizar un monorepositorio con Rust en la práctica: desde la creación de la estructura hasta la gestión de dependencias y las pruebas. Aprenderá cómo evitar errores típicos y hacer que su base de código sea escalable.
¿Qué es Cargo Workspace y por qué es necesario?
Cargo Workspace es un mecanismo que permite gestionar múltiples crates (bibliotecas o archivos ejecutables) dentro de un solo proyecto. En lugar de crear repositorios separados para cada componente, los unifica en una única estructura. Esto ofrece varias ventajas:
- Sistema de compilación unificado — un solo comando
cargo buildcompila todos los crates. - Dependencias compartidas — las dependencias duplicadas se resuelven una vez, lo que reduce el tamaño del binario final.
- Pruebas simplificadas — se pueden ejecutar pruebas para todos los crates con un solo comando.
- Consistencia de versiones — todos los componentes utilizan las mismas versiones de las bibliotecas.
Un monorepositorio es especialmente útil para microservicios, bibliotecas de propósito general o proyectos con una clara separación en capas (por ejemplo, núcleo, API, CLI).
Creación de la estructura del monorepositorio
Comencemos con un ejemplo práctico. Supongamos que estamos desarrollando un sistema de gestión de tareas. Tendremos tres crates:
core— biblioteca con la lógica de negocio.cli— archivo ejecutable para la línea de comandos.server— servidor web (archivo ejecutable).
Primero, crearemos el directorio raíz y el archivo Cargo.toml para el workspace:
mkdir task_managercd task_manager# Creamos el Cargo.toml raíztouch Cargo.tomlEn el Cargo.toml raíz, especificamos el workspace:
[workspace]members = [ "core", "cli", "server",]Ahora, creamos cada crate con cargo new:
cargo new core --libcargo new clicargo new serverDespués de esto, la estructura se verá así:
task_manager/├── Cargo.toml # Workspace raíz├── core/│ ├── Cargo.toml│ └── src/│ └── lib.rs├── cli/│ ├── Cargo.toml│ └── src/│ └── main.rs└── server/ ├── Cargo.toml └── src/ └── main.rsTenga en cuenta: el Cargo.toml raíz no contiene la sección [package], solo [workspace]. Esto es importante para un funcionamiento correcto.
Gestión de dependencias entre crates
Una de las tareas clave en un monorepositorio es configurar correctamente las dependencias entre crates. En nuestro ejemplo, cli y server dependen de core. Especifiquemos esto en sus Cargo.toml:
Para cli/Cargo.toml:
[package]name = "cli"version = "0.1.0"edition = "2021"
[dependencies]core = { path = "../core" }clap = { version = "4.0", features = ["derive"] } # Dependencia externa para CLIPara server/Cargo.toml:
[package]name = "server"version = "0.1.0"edition = "2021"
[dependencies]core = { path = "../core" }actix-web = "4" # Dependencia externa para el servidor webEn el Cargo.toml raíz también se pueden especificar dependencias comunes si son necesarias para todos los crates, pero normalmente esto se hace de forma individual. Sin embargo, el workspace permite resolver conflictos de versiones: si dos crates usan la misma biblioteca, Cargo automáticamente usará una sola versión.