Einführung
Ein Monorepository (Monorepo) ist ein Ansatz zur Verwaltung von Quellcode, bei dem mehrere Projekte (Bibliotheken, Anwendungen, Dienste) in einem einzigen Repository gespeichert werden. Im Rust-Ökosystem wird dieser Ansatz durch den Mechanismus Cargo Workspace umfassend unterstützt. In diesem Artikel werden wir untersuchen, wie man ein Monorepository in Rust effektiv organisiert, und uns mit der Struktur, dem Abhängigkeitsmanagement und den Best Practices befassen.
Warum ist ein Monorepository für Rust-Projekte nützlich? Es ermöglicht:
- Die Wiederverwendung von Code zwischen mehreren Crates (Bibliotheken und Binärdateien).
- Die Vereinfachung von Versionierung und CI/CD.
- Die Sicherstellung von atomaren Änderungen (ein einzelner Commit kann mehrere Crates aktualisieren).
- Die Reduzierung der Komplexität des Abhängigkeitsmanagements.
Grundlagen von Cargo Workspace
Cargo Workspace ist der integrierte Mechanismus von Rust zur Verwaltung eines Monorepositorys. Er fasst mehrere Crates unter einer gemeinsamen übergeordneten Cargo.toml-Datei zusammen.
Erstellen eines Workspace
Zunächst erstellen wir ein Projektverzeichnis und eine Cargo.toml-Datei im Stammverzeichnis:
# Cargo.toml (Stammverzeichnis)[workspace]members = [ "core", "cli", "web-api", "utils"]
[workspace.dependencies]serde = { version = "1.0", features = ["derive"] }tokio = { version = "1.37", features = ["full"] }Hier haben wir vier Crates definiert: core, cli, web-api und utils. Der entscheidende Punkt ist [workspace.dependencies]. Dies ermöglicht es, gemeinsame Abhängigkeiten für alle Crates im Workspace festzulegen. Jeder Crate kann darauf verweisen, ohne die Versionen zu duplizieren.
Verzeichnisstruktur
Eine typische Struktur eines Monorepositorys in Rust sieht wie folgt aus:
my-monorepo/├── Cargo.toml # Stammverzeichnis-Workspace├── Cargo.lock # Gemeinsame Lock-Datei├── 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.mdJedes Unterverzeichnis ist ein eigener Crate mit seiner eigenen Cargo.toml. Crates können sowohl Bibliotheken (lib.rs) als auch ausführbare Anwendungen (main.rs) sein.
Abhängigkeitsmanagement und Code-Wiederverwendung
Interne Abhängigkeiten
Crates innerhalb eines Workspace können über lokale Pfade aufeinander verweisen. Beispielsweise kann cli von core und utils abhängen:
# 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 }Beachten Sie die Syntax { workspace = true } – sie ermöglicht die Verwendung gemeinsamer Abhängigkeiten, die in der Stammverzeichnis-Cargo.toml deklariert sind. Dies stellt sicher, dass alle Crates dieselbe Version der Bibliothek verwenden.
Beispiel: Gemeinsame Bibliothek core
Erstellen wir eine einfache Bibliothek core, die die Geschäftslogik enthält:
// 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)}Jetzt kann der Crate cli diese Bibliothek verwenden:
// cli/src/main.rsuse core::{create_user, greet};
fn main() { let user = create