Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Architecture de projets à grande échelle (workspaces)

Lorsque la taille d’un projet Rust augmente, regrouper tout le code dans une seule et unique crate est contre-productif : les temps de compilation s’allongent et la maintenabilité se dégrade.

Pour structurer de grandes applications ou des monorepos, Cargo intègre nativement le concept d’espaces de travail (workspaces).

Un espace de travail permet de diviser un projet en plusieurs crates indépendantes (modules, bibliothèques, binaires) tout en partageant le même fichier de verrouillage (Cargo.lock), le même répertoire de sortie (target), et des configurations communes.

Structure d’un Workspace Cargo

Un espace de travail est défini par un fichier Cargo.toml racine. Ce fichier ne définit pas une crate en soi, mais orchestre les membres du workspace.

Voici l’architecture type d’un projet d’envergure découpé en Workspace :

mon_projet_workspace/
├── Cargo.toml               # Fichier de configuration du Workspace
├── Cargo.lock               # Partagé par toutes les crates
├── target/                  # Répertoire de build unique et partagé
├── applications/
│   ├── api_web/             # Crate binaire (application finale)
│   │   └── Cargo.toml
│   └── outil_cli/           # Autre crate binaire
│       └── Cargo.toml
└── composants/
    ├── base_donnees/        # Crate bibliothèque (logique interne)
    │   └── Cargo.toml
    └── modeles/             # Crate bibliothèque (structures de données)
        └── Cargo.toml

Le fichier Cargo.toml à la racine déclare explicitement les dossiers contenant les sous-crates :

[workspace]
members = [
    "applications/*",
    "composants/*"
]

# Centralisation optionnelle mais recommandée des dépendances du Workspace
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
modeles = { path = "composants/modeles" } # Les membres peuvent aussi être centralisés ici

Gestion des dépendances entre membres

Pour qu’un membre du workspace utilise un autre membre (par exemple, l’application api_web qui a besoin de la bibliothèque modeles), on utilise des chemins relatifs ou la centralisation du workspace.

Dans applications/api_web/Cargo.toml, on hérite des dépendances centralisées grâce au mot-clé workspace = true :

[package]
name = "api_web"
version = "0.1.0"
edition = "2021"

[dependencies]
# Utilise les versions exactes définies au niveau de la racine du workspace
tokio = { workspace = true }
serde = { workspace = true }
modeles = { workspace = true }

Cette approche garantit que l’intégralité du projet utilise exactement les mêmes versions des bibliothèques tierces. Cela permet d’éviter les conflits de types et d’optimiser les temps de compilation.

Commandes Cargo au niveau de l’espace de travail

Cargo est conçu pour comprendre la topologie de l’espace de travail.

Les commandes exécutées à la racine s’appliquent par défaut à tous les membres.

  • Compiler tout l’espace de travail : cargo build
  • Exécuter les tests de toutes les sous-crates : cargo test
  • Compiler ou exécuter un membre spécifique :
cargo run -p api_web
cargo test -p composants_base_donnees

-p pour package (paquet)

Stratégies d’architecture pour le découpage

Pour maintenir un projet à grande échelle sain, le découpage en crates doit suivre des règles strictes d’architecture logicielle :

  • Crates de domaine (domain crates) : Destinées uniquement à la logique métier pure et aux structures de données. Elles doivent être vierges de toute dépendance technique lourde (pas de framework web, pas de driver de base de données direct).
  • Crates d’infrastructure (infrastructure crates) : Gèrent les détails techniques (implémentation SQLX, connecteurs API externes). Elles implémentent souvent des traits définis par les crates de domaine.
  • Crates d’application (application crates) : Les points d’entrée du programme (CLI, serveurs HTTP). Elles assemblent les briques d’infrastructure et injectent les dépendances requises par le domaine.

Ce découpage strict accélère la compilation : si nous modifions le code de l’application api_web, Cargo n’a pas besoin de recompiler la crate modeles ou base_donnees si leurs fichiers n’ont pas été modifiés.