Monorepo avec Rust : organisation du code et meilleures pratiques

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction



Un monorepo (monorepository) est une approche de gestion du code source où plusieurs projets (bibliothèques, applications, services) sont stockés dans un seul dépôt. Dans l'écosystème Rust, cette approche bénéficie d'un support puissant via le mécanisme Cargo Workspace. Dans cet article, nous allons voir comment organiser efficacement un monorepo en Rust, en examinant la structure, la gestion des dépendances et les meilleures pratiques.



Pourquoi un monorepo est-il utile pour les projets Rust ? Il permet de :

  • Réutiliser le code entre plusieurs crates (bibliothèques et binaires).
  • Simplifier le versioning et le CI/CD.
  • Assurer l'atomicité des modifications (un seul commit peut mettre à jour plusieurs crates).
  • Réduire la complexité de la gestion des dépendances.


Bases de Cargo Workspace



Cargo Workspace est le mécanisme intégré de Rust pour gérer un monorepo. Il regroupe plusieurs crates sous un seul fichier Cargo.toml de niveau supérieur.



Création d'un workspace



Pour commencer, créons un répertoire de projet et un fichier Cargo.toml à la racine :



# Cargo.toml (racine)[workspace]members = [    "core",    "cli",    "web-api",    "utils"]

[workspace.dependencies]serde = { version = "1.0", features = ["derive"] }tokio = { version = "1.37", features = ["full"] }


Ici, nous avons défini quatre crates : core, cli, web-api et utils. Le point clé est [workspace.dependencies]. Cela permet de définir des dépendances communes pour tous les crates du workspace. Chaque crate peut y faire référence sans dupliquer les versions.



Structure des répertoires



Une structure typique de monorepo en Rust ressemble à ceci :



my-monorepo/├── Cargo.toml          # workspace racine├── Cargo.lock          # fichier de verrouillage commun├── core/│   ├── Cargo.toml│   └── src/│       └── lib.rs├── cli/│   ├── Cargo.toml│   └── src/│       └── main.rs├── web-api/│   ├── Cargo.toml│   └── src/│       └── main.rs├── utils/│   ├── Cargo.toml│   └── src/│       └── lib.rs└── README.md


Chaque sous-répertoire est un crate distinct avec son propre Cargo.toml. Les crates peuvent être des bibliothèques (lib.rs) ou des applications exécutables (main.rs).



Gestion des dépendances et réutilisation du code



Dépendances internes



Les crates à l'intérieur d'un workspace peuvent se référer les uns aux autres via des chemins locaux. Par exemple, cli peut dépendre de core et utils :



# cli/Cargo.toml[package]name = "cli"version = "0.1.0"edition = "2021"

[dependencies]core = { path = "../core" }utils = { path = "../utils" }serde = { workspace = true }tokio = { workspace = true }


Notez la syntaxe { workspace = true } — elle permet d'utiliser les dépendances communes déclarées dans le Cargo.toml racine. Cela garantit que tous les crates utilisent la même version de la bibliothèque.



Exemple : bibliothèque partagée core



Créons une bibliothèque simple core qui contiendra la logique métier :



// core/src/lib.rsuse serde::{Serialize, Deserialize};

#[derive(Debug, Clone, Serialize, Deserialize)]pub struct User { pub id: u64, pub name: String, pub email: String,}

pub fn create_user(name: String, email: String) -> User { User { id: rand::random(), name, email, }}

pub fn greet(user: &User) -> String { format!("Hello, {}!", user.name)}


Maintenant, le crate cli peut utiliser cette bibliothèque :



// cli/src/main.rsuse core::{create_user, greet};

fn main() { let user = create

Blogs

Book Recommendations