Introducción
A medida que un proyecto en Rust crece, surge la pregunta: ¿cómo organizar el código para que se mantenga mantenible, escalable y conveniente para el equipo? Un enfoque popular es el monorepositorio (monorepo). En este artículo, analizaremos qué es un monorepositorio, cómo organizarlo usando Cargo workspace y proporcionaremos ejemplos prácticos. Aprenderás cómo estructurar el código, gestionar dependencias y evitar errores comunes.
¿Qué es un monorepositorio y por qué es necesario en Rust?
Un monorepositorio (o monorepo) es un repositorio único que contiene múltiples proyectos (crates) o paquetes. A diferencia de los multirrepositorios, donde cada proyecto vive por separado, un monorepositorio lo combina todo: librerías, binarios, pruebas, configuraciones y documentación.
Ventajas de un monorepositorio para Rust:
- Versión única de dependencias — todos los crates usan las mismas versiones de paquetes externos, lo que reduce conflictos.
- Refactorización simplificada — los cambios en una librería son visibles inmediatamente en todos los crates dependientes.
- Integración de extremo a extremo — puedes ejecutar fácilmente pruebas y linting para todo el proyecto.
- Documentación centralizada — todos los documentos y CI/CD están en un solo lugar.
Sin embargo, un monorepositorio no es adecuado para todos. Para proyectos pequeños o equipos con diferentes ciclos de desarrollo, es mejor elegir un multirrepositorio. Pero para el ecosistema de Rust, donde la modularidad y la reutilización de código son la norma, un monorepositorio se convierte en una herramienta poderosa.
Organizando un monorepositorio con Cargo Workspace
Cargo soporta el mecanismo workspace — este es un grupo de crates que se construyen juntos y comparten un Cargo.lock común. Veamos la estructura básica.
Creando un workspace
Crea un directorio raíz y un archivo Cargo.toml sin la sección [package], pero con la sección [workspace]:
# root Cargo.toml[workspace]members = [ "core", "cli", "web",]Ahora crea los subdirectorios core, cli, web. Cada uno debe tener su propio Cargo.toml con una estructura de paquete típica. Por ejemplo, para core:
# core/Cargo.toml[package]name = "my_core"version = "0.1.0"edition = "2021"
[dependencies]serde = { version = "1.0", features = ["derive"] }Para cli y web, puedes especificar una dependencia de my_core:
# cli/Cargo.toml[package]name = "my_cli"version = "0.1.0"edition = "2021"
[dependencies]my_core = { path = "../core" }clap = { version = "4.0", features = ["derive"] }Ahora cuando ejecutes cargo build desde la raíz, todos los crates se construirán y Cargo.lock será compartido.
Estructura de carpetas y módulos
Estructura de monorepositorio recomendada para Rust:
my_monorepo/├── Cargo.toml # workspace├── Cargo.lock├── core/ # librería con tipos comunes│ ├── Cargo.toml│ └── src/│ └── lib.rs├── cli/ # aplicación de consola│ ├── Cargo.toml│ └── src/│ └── main.rs├── web/ # servidor web (ej. en Actix)│ ├── Cargo.toml│ └── src/│ └── main.rs├── tests/ # pruebas de integración (opcional)│ └── integration_test.rs├── scripts/ # scripts para CI/CD└── README.mdDentro de cada crate, usa la estructura de módulos estándar de Rust. Por ejemplo, en core/src/lib.rs:
// core/src/lib.rspub mod models;pub mod utils;Y en core/src/models.rs:
// core/src/models.rsuse serde::{Serialize, Deserialize};
#[derive(Debug, Serialize, Deserialize)]pub struct