Monorepositório com Rust: organização de código na prática
Monorepositório (monorepo) é uma abordagem de gerenciamento de código onde vários projetos ou componentes são armazenados em um único repositório. No ecossistema Rust, essa prática é especialmente popular devido ao suporte nativo do Cargo Workspace. Neste artigo, vamos explorar como organizar um monorepositório com Rust na prática: desde a criação da estrutura até o gerenciamento de dependências e testes. Você aprenderá como evitar erros comuns e tornar sua base de código escalável.
O que é Cargo Workspace e por que ele é necessário
Cargo Workspace é um mecanismo que permite gerenciar vários crates (bibliotecas ou executáveis) dentro de um único projeto. Em vez de criar repositórios separados para cada componente, você os une em uma estrutura única. Isso oferece várias vantagens:
- Sistema de build unificado — um único comando
cargo buildcompila todos os crates. - Dependências compartilhadas — dependências duplicadas são resolvidas uma vez, reduzindo o tamanho do binário final.
- Testes simplificados — é possível executar testes para todos os crates com um único comando.
- Consistência de versões — todos os componentes usam as mesmas versões das bibliotecas.
O monorepositório é especialmente útil para microsserviços, bibliotecas de uso geral ou projetos com uma divisão clara em camadas (por exemplo, core, API, CLI).
Criando a estrutura do monorepositório
Vamos começar com um exemplo prático. Suponha que estamos desenvolvendo um sistema de gerenciamento de tarefas. Teremos três crates:
core— biblioteca com a lógica de negócios.cli— executável para linha de comando.server— servidor web (executável).
Primeiro, criamos o diretório raiz e o arquivo Cargo.toml para o workspace:
mkdir task_managercd task_manager# Criando o Cargo.toml raiztouch Cargo.tomlNo Cargo.toml raiz, especificamos o workspace:
[workspace]members = [ "core", "cli", "server",]Agora, criamos cada crate com cargo new:
cargo new core --libcargo new clicargo new serverApós isso, a estrutura ficará assim:
task_manager/├── Cargo.toml # Workspace raiz├── core/│ ├── Cargo.toml│ └── src/│ └── lib.rs├── cli/│ ├── Cargo.toml│ └── src/│ └── main.rs└── server/ ├── Cargo.toml └── src/ └── main.rsObserve: o Cargo.toml raiz não contém a seção [package], apenas [workspace]. Isso é importante para o funcionamento correto.
Gerenciando dependências entre crates
Uma das tarefas principais em um monorepositório é configurar corretamente as dependências entre os crates. No nosso exemplo, cli e server dependem de core. Vamos especificar isso nos seus 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"] } # Dependência externa para CLIPara server/Cargo.toml:
[package]name = "server"version = "0.1.0"edition = "2021"
[dependencies]core = { path = "../core" }actix-web = "4" # Dependência externa para o servidor webNo Cargo.toml raiz, também é possível especificar dependências comuns, se todos os crates precisarem delas, mas geralmente isso é feito individualmente. No entanto, o workspace permite resolver conflitos de versão: se dois crates usam a mesma biblioteca, o Cargo automaticamente usará uma única versão.