Introdução
À medida que um projeto Rust cresce, surge a pergunta: como organizar o código para que ele permaneça sustentável, escalável e conveniente para a equipe? Uma abordagem popular é o monorrepositório (monorepo). Neste artigo, vamos detalhar o que é um monorrepositório, como organizá-lo usando Cargo workspace e fornecer exemplos práticos. Você aprenderá como estruturar código, gerenciar dependências e evitar erros comuns.
O que é um monorrepositório e por que ele é necessário no Rust?
Um monorrepositório (ou monorepo) é um único repositório que contém vários projetos (crates) ou pacotes. Ao contrário dos multirrepositórios, onde cada projeto vive separadamente, um monorrepositório combina tudo: bibliotecas, binários, testes, configurações e documentação.
Vantagens de um monorrepositório para Rust:
- Versão única de dependências — todos os crates usam as mesmas versões de pacotes externos, o que reduz conflitos.
- Refatoração simplificada — alterações em uma biblioteca são imediatamente visíveis em todos os crates dependentes.
- Integração de ponta a ponta — você pode executar facilmente testes e linting para todo o projeto.
- Documentação centralizada — todos os documentos e CI/CD estão em um só lugar.
No entanto, um monorrepositório não é adequado para todos. Para projetos pequenos ou equipes com ciclos de desenvolvimento diferentes, é melhor escolher um multirrepositório. Mas para o ecossistema Rust, onde modularidade e reutilização de código são a norma, um monorrepositório se torna uma ferramenta poderosa.
Organizando um monorrepositório com Cargo Workspace
O Cargo suporta o mecanismo workspace — este é um grupo de crates que são compilados juntos e compartilham um Cargo.lock comum. Vamos analisar a estrutura básica.
Criando um workspace
Crie um diretório raiz e um arquivo Cargo.toml sem a seção [package], mas com a seção [workspace]:
# root Cargo.toml[workspace]members = [ "core", "cli", "web",]Agora crie os subdiretórios core, cli, web. Cada um deles deve ter seu próprio Cargo.toml com uma estrutura de pacote típica. Por exemplo, para core:
# core/Cargo.toml[package]name = "my_core"version = "0.1.0"edition = "2021"
[dependencies]serde = { version = "1.0", features = ["derive"] }Para cli e web, você pode especificar uma dependência em 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"] }Agora, ao executar cargo build a partir da raiz, todos os crates serão compilados e o Cargo.lock será compartilhado.
Estrutura de pastas e módulos
Estrutura de monorrepositório recomendada para Rust:
my_monorepo/├── Cargo.toml # workspace├── Cargo.lock├── core/ # biblioteca com tipos comuns│ ├── Cargo.toml│ └── src/│ └── lib.rs├── cli/ # aplicação de console│ ├── Cargo.toml│ └── src/│ └── main.rs├── web/ # servidor web (ex.: no Actix)│ ├── Cargo.toml│ └── src/│ └── main.rs├── tests/ # testes de integração (opcional)│ └── integration_test.rs├── scripts/ # scripts para CI/CD└── README.mdDentro de cada crate, use a estrutura de módulos padrão do Rust. Por exemplo, em core/src/lib.rs:
// core/src/lib.rspub mod models;pub mod utils;E em core/src/models.rs:
// core/src/models.rsuse serde::{Serialize, Deserialize};
#[derive(Debug, Serialize, Deserialize)]pub struct