PRODSouveräne europäische BaaS-PlattformÖffnen Sie das Dashboard →

Ingenieurwesen · 10 Min. Lesezeit

Cargo-Arbeitsbereiche für ein Rust-Backend mit mehreren Diensten

Affane Daylami · Fondateur · 12. August 2026

Zurück zum Blog

Ein Multi-Service-Backend in Rust wirft schnell die gleiche Frage auf: ein Depot pro Service oder ein einzelner Cargo-Arbeitsbereich? Aurabase entschied sich für die zweite Option. Elf Dienste, eine CLI und fünf gemeinsam genutzte Bibliotheken befinden sich in einem einzigen Cargo.toml-Stammverzeichnis, mit einem einzigen Cargo.lock für alle. So ist dieser Arbeitsbereich aufgebaut, wenn man ihn Zeile für Zeile liest.

Dieser englische Text wurde automatisch aus dem französischen Original generiert und wurde noch nicht überprüft.
Diese Seite wurde automatisch übersetzt. Maßgeblich ist die englische Version.

Dies ist kein pädagogisches Beispiel, das für diesen Anlass erfunden wurde. Jeder Codeausschnitt unten stammt aus dem Aurabase-Stammverzeichnis Cargo.toml und seinen Crate-Manifesten, wie sie heute im Repository vorhanden sind – einschließlich zweier Unstimmigkeiten, die wir beim erneuten Lesen für diesen Artikel festgestellt haben und die wir unverändert dokumentieren, anstatt sie stillschweigend vor der Veröffentlichung zu beheben.

Das Wesentliche
  • Der Aurabase-Root-Cargo-Arbeitsbereich deklariert 18 explizite Mitglieder: 11 Dienste, die aura-cliCLI, 5 gemeinsam genutzte Bibliotheken und das aurabase-rs SDK – unter resolver = "2" und einem einzelnen Cargo.lock.
  • [workspace.dependencies] zentralisiert gemeinsam genutzte Versionen; Jede Kiste erbt mit { workspace = true }, anstatt ihre eigene Nummer festzulegen – außer wenn eine Kiste stillschweigend überschreibt.
  • Ein Ordner unter services/ ist nicht automatisch Mitglied des Arbeitsbereichs: Die Liste members ist explizit und kein Glob, gerade um Nicht-Rust-Code ausschließen zu können.
  • [profile.release] gilt nur einmal für den gesamten Arbeitsbereich – eine Auswahl wie panic = "unwind" gilt dann für alle elf Dienste gleichzeitig.
#
Warum ein Arbeitsbereich

Ein Depot pro Service oder nur ein Cargo.lock?

Ein Workspace Cargo gruppiert mehrere Crates unter einem einzigen Cargo.lock und einem einzigen Zielverzeichnis – genau die Funktionalität, die der Paketmanager von Rust für diesen Fall bereitstellt. Elf separate Rust-Binärdateien, jede in ihrem eigenen Repository, wirken auf den ersten Blick unabhängiger. In der Praxis bedeutet dies elf verschiedene Cargo.lock, elf Versionsauflösungen, die im Laufe der Zeit abweichen können, und keine Garantie dafür, dass zwei Dienste dieselbe Version vonaxum oder sqlxkompilieren.

Ein Cargo-Arbeitsbereich löst dieses Problem auf der Ebene des Paketmanagers, nicht auf der Ebene der Teamdisziplin. Alle Mitglieder teilen sich ein einziges Cargo.lock im Stammverzeichnis: Eine gemeinsame Abhängigkeit wird einmal für das gesamte Diagramm in eine identische Version aufgelöst. Dadurch wird auch ein dienstübergreifender Refactor (z. B. Ändern einer Signatur in aura-core) für einen einzelnen cargo build --workspacesichtbar und nicht für jeden einzelnen Dienst in der Produktion erkannt. Dies ist eine der Entscheidungen, die unseren 100 % einheitlichen Rust-Kern von einem heterogenen -Stack unterscheidet, der von Dienst zu Dienstzusammengestellt wird.

#
Anatomie

Das Stammverzeichnis von Cargo.toml: Resolver und Mitglieder

Alles beginnt mit der Deklaration [workspace] und ihrer Liste members. Bei Aurabase ist diese Liste handgeschrieben und nach Rollen gruppiert – Dienste, Tools, Bibliotheken – und nicht nach einem generischen Muster wie services/*generiert.

Cargo.tomltoml
[workspace]
resolver = "2"

members = [
    # Dienstleistungen
    "services/aura-gateway",
    "services/aura-auth",
    "services/aura-db",
    # … 8 weitere Dienste (Aura-Realtime, Aura-Storage, Aura-AI, …)
    # Werkzeuge
    "aura-cli",
    # Bibliotheken
    "libs/aura-core",
    # …4 weitere Bibliotheken
    "aurabase-rs",
]

resolver = "2" ist kein kosmetisches Detail. Der -Resolver v2 von Cargo isoliert die Funktionalität von build-dependencies und zielspezifischen Abhängigkeiten (z. B.target.'cfg(windows)') vom Rest des Diagramms – sie dringen nicht mehr in die endgültige Binärdatei ein. Es vereinheitlicht außerdem die Versionen derselben Abhängigkeit für alle Mitglieder des Arbeitsbereichs, die sie gemeinsam nutzen: ein einziges axum, nur einmal, nicht elf unabhängige Auflösungen.

#
Vermächtnis

workspace.package: eine Version, eine Edition, grundsätzlich geteilt

[workspace.package] deklariert einmal die gemeinsamen Felder – Version, Edition, Autoren, Lizenz – die jede Kiste mit version.workspace = true erben kann, anstatt sie zu kopieren.

Cargo.toml (root)toml
[workspace.package]
version = "0.1.1"
edition = "2021"

# Services/aura-gateway/Cargo.toml
[package]
name = "aura-gateway"
version.workspace = true

Die elf Dienste von Aurabase folgen alle diesem Muster. Zwei Crates weichen ab, und dies ist die erste der beiden Diskrepanzen, die bei der Erstellung dieses Artikels festgestellt wurden: aura-cli deklariert version = "0.2.0" in gedruckter Form, und die Bibliothek libs/aura-migrations deklariert version = "0.1.0" – beide unterscheiden sich von 0.1.1 des Arbeitsbereichs.

Was es bedeutet

version.workspace = true ist optional, Feld für Feld, Kiste für Kiste. Nichts hindert eine Kiste daran, ihre eigene Nummerierung beizubehalten – freiwillig (z. B. ein separat veröffentlichtes Tool) oder aus Vergesslichkeit. Eine Arbeitsbereichsprüfung sollte dieses Feld Kiste für Kiste überprüfen und keine Vererbung annehmen.

#
Deduplizierung

workspace.dependencies: eine Quelle der Wahrheit, es sei denn, eine Kiste umgeht sie

[workspace.dependencies] zentralisiert Abhängigkeiten, die von mehreren Crates gemeinsam genutzt werden. Jeder Dienst verweist mit { workspace = true } darauf, anstatt eine eigene Versionseinschränkung festzulegen.

Cargo.toml (root)toml
[workspace.dependencies]
axum = { version = "0.8", features = ["ws", "multipart", "macros"] }
sqlx = { version = "0.8", default-features = false, features = […] }
tower_governor = { version = "0.8", features = ["axum"] }
governor = "0.10"

# Services/aura-gateway/Cargo.toml – erbt NICHT den Workspace Governor
governor = "0.8"  # lokale Version, anders

Dies ist die zweite wirkliche Diskrepanz: Der Arbeitsbereich zentralisiert governor in Version 0.10, aber aura-gateway deklariert seine eigene Zeile governor = "0.8" neu, anstatt zu erben – das Gateway wendet seine Ratenbegrenzung mit der bloßen Kiste governor an, wenn aura-auth, aura-ai, aura-db und aura-functions passieren tower_governor in der Middleware. Ein Arbeitsbereich verhindert Divergenz nicht: Er macht sie nur dann sichtbar, wenn wir uns die Mühe machen, sie zu vergleichen.

Allerdings verdienen nicht alle Abhängigkeiten eine Zentralisierung. wasmtime erscheint nirgendwo in [workspace.dependencies]: Nur eine Kiste, aura-functions, verwendet es für seine WASM-Laufzeit, sodass es lokal deklariert bleibt. Die Regel, die wir anwenden: Erhöhen Sie eine Abhängigkeit auf die Arbeitsbereichsebene, sobald zwei oder mehr Crates sie gemeinsam nutzen, nicht vorher.

#
Internes Diagramm

libs/ zu Services/: was davon abhängt, was

Die fünf gemeinsam genutzten Bibliotheken (aura-core, aura-crypto, aura-db-adapters, aura-migrations, aura-telemetry) werden als Pfadabhängigkeiten in [workspace.dependencies] – aura-core = { path = "libs/aura-core" } deklariert – dann wählt jeder Dienst aus, welche er tatsächlich benötigt.

Das resultierende Diagramm bleibt von einer Datei zur anderen lesbar. aura-gateway hängt nur von drei der fünf internen Bibliotheken ab – aura-core, aura-crypto und aura-telemetry. Genug zum Routing, Signieren eines JWT für den PostgREST-Sidecar und Exportieren seiner Traces, ohne aura-db-adapters oder aura-migrationszu berühren. aura-provisionerfügt aura-migrationshinzu: Es spielt die Schemamigrationen ab, die sich aus der Erstellung eines Projekts ergeben.

Der engste Fall ist aura-migrator, die dedizierte Binärdatei, die CI/CD-Migrationen durchführt. Unter den internen Aurabase-Bibliotheken listet die Produktion [dependencies] nuraura-migrations auf – aura-core erscheint zu Testzwecken nur in [dev-dependencies]. Die an die Produktion gelieferte Binärdatei enthält daher keinenaura-core-Code; es existiert nur während cargo test.

#
Konkreter Fall

Eine Kiste, mehrere Binärdateien: Aura-Echtzeit-Aufteilung

Für einen Arbeitsbereich muss nicht jedes Mal ein neues Mitglied erstellt werden, wenn Sie einen neuen bereitstellbaren Prozess wünschen. aura-realtime bleibt ein einzelnes Mitglied des Arbeitsbereichs, aber sein Cargo.toml deklariert drei verschiedene [[bin]]-Tabellen um denselben gemeinsamen [lib].

Cargo.tomltoml
[lib]
name = "aura_realtime"

[[bin]]
name = "aura-realtime"

[[bin]]
name = "aura-realtime-cdc-worker"
path = "src/bin/cdc_worker.rs"

[[bin]]
name = "aura-realtime-ws-front"
path = "src/bin/ws_front.rs"

Das Manifest selbst dokumentiert die Grenze zwischen beiden. cdc-worker erfasst Postgres-Änderungen durch Abfragen und veröffentlicht sie auf NATS im Pub/Sub-Kern (Client::publish_with_headers), ohne jemals einen WebSocket-Server zu öffnen – JetStream bedient in demselben Dienst ausschließlich die instanzübergreifende Präsenz-KV, nicht den CDC-Fan-Out. ws-front verbraucht nur NATS und hält WebSocket/SSE-Verbindungen, ohne jemals den CDC zu berühren – die vollständigen Details finden Sie in derEchtzeit-Engine-Dokumentation. Beide Binärdateien nutzen über [lib]denselben RLS-Filtercode, werden jedoch unabhängig voneinander in Kubernetes bereitgestellt und skaliert. Dies ist das richtige Signal, mehrere Binärdateien in einer Kiste anstelle eines neuen Arbeitsbereichsmitglieds auszuwählen: gleiche interne Logik, unterschiedliche Bereitstellungstopologien.

#
Falle, die es zu vermeiden gilt

Eine Datei unter „services/“ ist nicht unbedingt ein Cargo-Mitglied

Der Ordner aura-edge-runtime ist im Aurabase-Repository vorhanden. Es enthält jedoch kein Cargo.toml – nur TypeScript (index.ts, envelope.ts), und es erscheint nirgendwo in der members-Liste des Root-Arbeitsbereichs.

Scharfsinn

Genau aus diesem Grund ist die members-Liste von Aurabase handgeschrieben und nicht durch ein generisches Muster wie services/*ersetzt. Ein Glob hätte versucht, diesen Nicht-Rust-Ordner in den Arbeitsbereich aufzunehmen, was zu einem Auflösungsfehler geführt hätte. Eine explizite Liste ermöglicht die Koexistenz von Ordnern, die nicht dieselbe Sprache sprechen, unter demselben übergeordneten Ordner services/.

Die Lektion verallgemeinert: Das Zählen der Dienste eines Rust-Backends durch Auflisten der Unterordner von services/ ergibt eine falsche Zahl. Nur der Stamm Cargo.toml ist maßgebend dafür, was tatsächlich im Arbeitsbereich kompiliert wird.

#
Zusammenstellung

[profile.release]: eine einzige Einstellung für den gesamten Arbeitsbereich

Ein im Stammverzeichnis des Arbeitsbereichs deklariertes [profile.release] gilt für alle im Release-Modus kompilierten Mitglieder – nur eine Stelle zum Anpassen, nicht elf. Cargo dokumentiert alle verfügbaren Schlüssel in seiner -Kompilierungsprofilreferenz; Aurabase aktiviert nur fünf.

Cargo.toml (root)toml
[profile.release]
lto = "thin"          # LTO-Cross-Crates, Leistung/Build-Zeit-Balance
codegen-units = 1     # insgesamt bestes Inlining
panic = "unwind"   # eine durch tokio/axum → 500 isolierte Panik, kein globaler Absturz
strip = "symbols"
opt-level = 3

Die Wahl von panic = "unwind" anstelle von "abort" wird direkt im Dateikommentar dokumentiert: Eine Panik in einem Axum-Handler wird von tokio abgefangen, die Aufgabe gibt eine 500 zurück und der Prozess bedient weiterhin andere gleichzeitige Anfragen. Der Leistungsgewinn vonabort ist den Verlust der Isolation zwischen Abfragen nicht wert.

#
In der Praxis

Frieren Sie die Toolchain und die wichtigen Befehle ein

Ein einheitlicher Arbeitsbereich legt Abhängigkeitsversionen fest, jedoch nicht die Version des Compilers selbst. rust-toolchain.tomlim Stammverzeichnis friert die Toolchain (heutechannel = "1.93") für jeden direkten Aufruf von cargo oder rustc im Repository ein.

Diese Datei existiert genau deshalb, weil ein stiller Drift aufgetreten ist: Eine CI-Aktion hat den stable-Kanal des Tages installiert, ohne rust-toolchain.tomlzu lesen, während das Release-Docker-Image mit der eingefrorenen Version kompiliert wurde. Ein Commit könnte alle Tests mit dem heutigen stabilen Rust bestehen und dann beim Image-Build abbrechen – entdeckt nach der Zusammenführung, nicht vorher. rustuprespektiert diese Datei für jeden direkten Befehl: Es war die einzige notwendige Korrektur, ohne die CI-Workflows zu beeinträchtigen.

terminalbash
# Kompilieren Sie den gesamten Arbeitsbereich
cargo build --workspace

# Testen Sie eine einzelne Kiste (nicht den gesamten Arbeitsbereich)
cargo test -p aura-auth

# Strenge Flusen auf dem gesamten Arbeitsbereich, Warnungen = Fehler
cargo clippy --workspace --all-targets -- -D warnings

# Formatierung
cargo fmt --all

Ein letztes Detail für diejenigen, die ein Manifest lesen, bevor sie es ausführen: Die Kiste aura-cli kompiliert eine Binärdatei, deren Tabelle [[bin]] sie aurabaseund nicht auranennt. Der veröffentlichte npm-Wrapper @aurabase/clistellt die beiden Befehle aura und aurabase bereit, die beide auf dasselbe Skript verweisen. Der Name einer [[bin]]-Fracht, der Name der Kiste und der von einem NPM-Wrapper offengelegte Name sind drei verschiedene Dinge; Aus den anderen beiden lässt sich keines erraten.

#
Zusammenfassung

Checkliste: Wo was deklarieren?

Jedes Mal, wenn Sie eine Kiste zu einem Cargo-Arbeitsbereich hinzufügen, stehen fünf Entscheidungen an. Hier wird jedes deklariert, basierend auf dem oben behandelten Aurabase-Beispiel.

Liste der Mitglieder[Arbeitsbereich] MitgliederRoot – explizite Liste, niemals ein Glob
Geteilte Version/Ausgabe[workspace.package]Root – version.workspace = true, nach Kiste, optional
Abhängigkeit, die von mehr als 2 Kisten geteilt wird[workspace.dependencies]Root – dann { workspace = true } in jeder Kiste
Abhängigkeit von einem einzelnen Verbraucher[Abhängigkeiten] der KisteVor Ort, ohne den Arbeitsbereich zu durchlaufen
Profil erstellen[profile.release]Nur Root – gilt für alle Mitglieder

Um einen bestehenden Cargo-Arbeitsbereich zu prüfen – Ihren oder den eines Projekts, das Sie übernehmen – reichen vier Prüfungen aus, um die oben dokumentierten Unstimmigkeiten zu finden:

  1. Vergleichen Sie [workspace.package].version mit version jeder Kiste – ein anderer Wert ist nicht unbedingt ein Fehler, aber es lohnt sich, ihn zu dokumentieren.
  2. Vergleichen Sie [workspace.dependencies] mit den lokal von jeder Kiste deklarierten Abhängigkeiten – erkennen Sie die auf beiden Seiten vorhandenen Namen mit unterschiedlichen Versionen.
  3. Vergleichen Sie die members-Liste im Stammverzeichnis Cargo.toml mit den tatsächlichen Unterordnern im Repository – ein fehlender Ordner in members ist nicht unbedingt ein Versehen.
  4. Überprüfen Sie den tatsächlichen Namen jeder kompilierten Binärdatei ([[bin]] name), anstatt anzunehmen, dass er mit dem Crate-Namen oder dem von einem möglichen NPM-Wrapper bereitgestellten Namen übereinstimmt.

BEREIT ZUM EINSATZ?

Ihr Backend in fünf Minuten.

Keine Kreditkarte erforderlich · 500 MB kostenlos · 50.000 MAU