PRODSoeverein Europees BaaS-platformOpen Dashboard →

Techniek · 10 min gelezen

Cargo-werkruimtes voor een multi-service Rust backend

Affane Daylami · Fondateur · 12 augustus 2026

Terug naar blog

Een multi-service backend in Rust roept al snel dezelfde vraag op: één depot per dienst, of één Cargo-werkruimte? Aurabase koos voor de tweede optie. Elf services, een CLI en vijf gedeelde bibliotheken bevinden zich in één Cargo.toml-root, met één Cargo.lock voor iedereen. Hier ziet u hoe deze werkruimte is opgebouwd, waarbij u deze regel voor regel leest.

Deze Engelse tekst is automatisch gegenereerd op basis van het Franse origineel en is nog niet beoordeeld.
Deze pagina is automatisch vertaald. De Engelse versie is gezaghebbend.

Dit is geen educatief voorbeeld dat voor de gelegenheid is bedacht. Elk codefragment hieronder is afkomstig van de Aurabase-root Cargo.toml en de bijbehorende kratmanifesten, zoals ze vandaag de dag in de repository voorkomen - inclusief twee discrepanties die we ontdekten toen we ze voor dit artikel herlezen, en die we documenteren zoals ze zijn in plaats van ze stilletjes op te lossen vóór publicatie.

De essentie
  • De Aurabase root Cargo-werkruimte declareert 18 expliciete leden: 11 services, de aura-cliCLI, 5 gedeelde libs en de aurabase-rs SDK — onder resolver = "2" en een enkele Cargo.lock.
  • [workspace.dependencies] centraliseert gedeelde versies; elke krat erft met { workspace = true } in plaats van een eigen nummer in te stellen - behalve wanneer een krat stilletjes de overhand krijgt.
  • Een map onder services/ is niet automatisch lid van de werkruimte: de members-lijst is expliciet, geen klodder, juist om niet-Rust-code uit te kunnen sluiten.
  • [profile.release] is slechts één keer van toepassing op de gehele werkruimte; een keuze als panic = "unwind" is dan van toepassing op alle elf services tegelijk.
#
Waarom een werkruimte

Eén depot per dienst, of slechts één Cargo.lock?

Een -werkruimte Cargo groepeert meerdere kratten onder een enkele Cargo.lock en een enkele doelmap - precies de functionaliteit die Rust's pakketbeheerder voor dit geval biedt. Elf afzonderlijke Rust-binaire bestanden, elk in hun eigen repository, lijken op het eerste gezicht onafhankelijker. In de praktijk betekent dit elf verschillende Cargo.lock, elf versieresoluties die in de loop van de tijd kunnen afwijken, en geen garantie dat twee services dezelfde versie vanaxum of sqlxcompileren.

Een Cargo-werkruimte lost dit op op pakketmanagerniveau, niet op teamdisciplineniveau. Alle leden delen één enkele Cargo.lock in de root: een gemeenschappelijke afhankelijkheid wordt één keer opgelost, naar een identieke versie, voor de hele grafiek. Dit is ook wat een cross-service refactor (bijvoorbeeld het wijzigen van een handtekening in aura-core) zichtbaar maakt voor een enkele cargo build --workspace, in plaats van service per service in productie te ontdekken. Dit is een van de keuzes die onze 100% uniforme Rust-kern onderscheidt van een heterogene stapel die service voor serviceis samengesteld.

#
Anatomie

De Cargo.toml-root: oplosser en leden

Het begint allemaal met de declaratie [workspace] en de bijbehorende lijst members. Bij Aurabase is deze lijst met de hand geschreven, gegroepeerd op rol (services, tools, libs) in plaats van gegenereerd door een generiek patroon zoals services/*.

Cargo.tomltoml
[workspace]
resolver = "2"

members = [
    # Diensten
    "services/aura-gateway",
    "services/aura-auth",
    "services/aura-db",
    # … 8 andere diensten (aura-realtime, aura-opslag, aura-ai, …)
    # Gereedschap
    "aura-cli",
    # Libs
    "libs/aura-core",
    # …4 andere libs
    "aurabase-rs",
]

resolver = "2" is geen cosmetisch detail. Cargo's -resolver v2 isoleert de functionaliteit van build-dependencies en doelspecifieke afhankelijkheden (bijvoorbeeldtarget.'cfg(windows)') van de rest van de grafiek; ze lekken niet langer naar het uiteindelijke binaire bestand. Het verenigt ook de versies van dezelfde afhankelijkheid voor alle leden van de werkruimte die deze delen: een enkele axum, slechts één keer, en niet elf onafhankelijke resoluties.

#
erfenis

workspace.package: één versie, één editie, in principe gedeeld

[workspace.package] declareert één keer de gemeenschappelijke velden (versie, editie, auteurs, licentie) die elke krat kan erven met version.workspace = true in plaats van ze te kopiëren.

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

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

De elf diensten van Aurabase volgen allemaal dit patroon. Twee kratten wijken af, en dit is de eerste van de twee discrepanties die zijn gevonden tijdens het voorbereiden van dit artikel: aura-cli declareert version = "0.2.0" op papier, en de lib libs/aura-migrations declareert version = "0.1.0" — beide verschillend van de 0.1.1 van de werkruimte.

Wat het betekent

version.workspace = true is optioneel, veld voor veld, krat voor krat. Niets weerhoudt een krat ervan zijn eigen nummering te behouden – vrijwillig (een afzonderlijk gepubliceerd hulpmiddel bijvoorbeeld) of door vergeetachtigheid. Bij een audit van de werkruimte moet dit veld krat voor krat worden gecontroleerd, en mag er niet worden uitgegaan van overerving.

#
Ontdubbeling

workspace.dependencies: een bron van waarheid, tenzij een krat deze omzeilt

[workspace.dependencies] centraliseert afhankelijkheden die door verschillende kratten worden gedeeld. Elke service verwijst ernaar met { workspace = true } in plaats van een eigen versiebeperking in te stellen.

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 — neemt NIET de werkruimte-gouverneur over
governor = "0.8"  # lokale versie, anders

Dit is de tweede echte discrepantie: de werkruimte centraliseert governor in versie 0.10, maar aura-gateway geeft zijn eigen regel governor = "0.8" opnieuw aan in plaats van te erven. De gateway past zijn snelheidsbeperking toe op de kale governor-krat, wanneer aura-auth, aura-ai, aura-db en aura-functions passeer tower_governor in middleware. Een werkruimte voorkomt divergentie niet: zij maakt deze alleen zichtbaar als we de moeite nemen om te vergelijken.

Niet alle afhankelijkheden verdienen het echter om gecentraliseerd te worden. wasmtime verschijnt nergens in [workspace.dependencies]: slechts één krat, aura-functions, gebruikt het voor zijn WASM-runtime, dus het blijft lokaal gedeclareerd. De regel die we toepassen: verhoog een afhankelijkheid naar het niveau van de werkruimte vanaf het moment dat twee of meer kratten deze delen, en niet eerder.

#
Interne grafiek

libs/ to services/: wat ervan afhangt

De vijf gedeelde libs (aura-core, aura-crypto, aura-db-adapters, aura-migrations, aura-telemetry) worden gedeclareerd als padafhankelijkheden in [workspace.dependencies] — aura-core = { path = "libs/aura-core" } — waarna elke service kiest welke hij daadwerkelijk nodig heeft.

De resulterende grafiek blijft leesbaar van het ene bestand naar het andere. aura-gateway is alleen afhankelijk van drie van de vijf interne libs: aura-core, aura-crypto en aura-telemetry. Genoeg om te routeren, teken een JWT voor de PostgREST zijspan en exporteer de sporen ervan, zonder aura-db-adapters of aura-migrationsaan te raken. aura-provisionervoegt aura-migrationstoe: het herhaalt de schemamigraties die voortvloeien uit de creatie van een project.

Het smalste geval is aura-migrator, het speciale binaire bestand dat CI/CD-migraties uitvoert. Van de interne bibliotheken van Aurabase vermeldt de productie [dependencies] alleenaura-migrations — aura-core verschijnt alleen in zijn [dev-dependencies], om te testen. Het binaire bestand dat in productie wordt genomen, bevat daarom geenaura-core-code; het bestaat alleen tijdens cargo test.

#
Betonnen geval

Eén krat, meerdere binaire bestanden: aura-realtime verdeling

Voor een werkruimte hoeft u niet telkens een nieuw lid aan te maken wanneer u een nieuw inzetbaar proces wilt. aura-realtime blijft een enkel lid van de werkruimte, maar zijn Cargo.toml declareert drie verschillende [[bin]] tabellen rond dezelfde gedeelde [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"

Het manifest zelf documenteert de grens tussen de twee. cdc-worker legt Postgres-wijzigingen vast door middel van polling en publiceert naar NATS in pub/sub core (Client::publish_with_headers), zonder ooit een WebSocket-server te openen - JetStream bedient in dezelfde service uitsluitend de cross-instance-aanwezigheid KV, niet de CDC-fan-out. ws-front verbruikt alleen NATS en bevat WebSocket/SSE-verbindingen, zonder ooit de CDC aan te raken - de volledige details staan ​​in dereal-time engine-documentatie. Beide binaire bestanden delen dezelfde RLS-filtercode via [lib], maar worden onafhankelijk geïmplementeerd en geschaald in Kubernetes. Dit is het juiste signaal om meerdere binaire bestanden in een krat te kiezen in plaats van een nieuw werkruimtelid: dezelfde interne logica, verschillende implementatietopologieën.

#
Val om te vermijden

Een bestand onder services/ is niet noodzakelijkerwijs een Cargo-lid

De map aura-edge-runtime bestaat in de Aurabase-repository. Het bevat echter geen Cargo.toml — alleen TypeScript (index.ts, envelope.ts), en het verschijnt nergens in de members-lijst van de hoofdwerkruimte.

Astuce

Dit is precies waarom de members-lijst van Aurabase met de hand is geschreven in plaats van vervangen door een algemeen patroon zoals services/*. Een glob zou hebben geprobeerd deze niet-Rust-map in de werkruimte op te nemen, met als resultaat een oplossingsfout. Met een expliciete lijst kunnen mappen die niet dezelfde taal spreken naast elkaar bestaan, onder dezelfde ouder services/.

De les generaliseert: het tellen van de services van een Rust-backend door de submappen van services/ op te sommen, levert een vals getal op. Alleen de root Cargo.toml is bepalend voor wat er feitelijk in de werkruimte wordt gecompileerd.

#
Compilatie

[profile.release]: één instelling voor de gehele werkruimte

Een [profile.release] die in de hoofdmap van de werkruimte is gedeclareerd, is van toepassing op alle leden die zijn gecompileerd in de releasemodus - slechts één plaats om aan te passen, niet elf. Cargo documenteert alle beschikbare sleutels in de compilatieprofielreferentie; Aurabase activeert er slechts vijf.

Cargo.toml (root)toml
[profile.release]
lto = "thin"          # LTO cross-crates, balans tussen prestaties en bouwtijd
codegen-units = 1     # beste algehele inlining
panic = "unwind"   # een paniek geïsoleerd door tokio/axum → 500, geen wereldwijde crash
strip = "symbols"
opt-level = 3

De keuze voor panic = "unwind" in plaats van "abort" wordt direct gedocumenteerd in de bestandsopmerking: een paniek in een axum-handler wordt onderschept door tokio, de taak retourneert een 500 en het proces gaat door met het verwerken van andere gelijktijdige verzoeken. De prestatiewinst vanabort is het verlies aan isolatie tussen query's niet waard.

#
In de praktijk

Bevries de toolchain en de opdrachten die er toe doen

Een uniforme werkruimte stelt afhankelijkheidsversies in, maar niet de versie van de compiler zelf. rust-toolchain.tomlbevriest in de root de toolchain (channel = "1.93" vandaag) voor elke directe aanroep naar cargo of rustc in de repository.

Dit bestand bestaat precies omdat er een stille drift heeft plaatsgevonden: een CI-actie installeerde het stable-kanaal van de dag zonder rust-toolchain.tomlte lezen, terwijl de release Docker-image werd gecompileerd met de bevroren versie. Een commit zou alle tests kunnen doorstaan ​​met de huidige stabiele Rust, en vervolgens breken met de image-build - ontdekt na de samenvoeging, niet ervoor. rustuprespecteert dit bestand voor elke directe opdracht: het was de enige noodzakelijke oplossing, zonder de CI-workflows te beïnvloeden.

terminalbash
# Compileer de volledige werkruimte
cargo build --workspace

# Test een enkele krat (niet de hele werkruimte)
cargo test -p aura-auth

# Strenge pluisjes op de hele werkruimte, waarschuwingen = fouten
cargo clippy --workspace --all-targets -- -D warnings

# Opmaak
cargo fmt --all

Nog een laatste detail, voor degenen die een manifest lezen voordat ze het uitvoeren: de aura-cli-krat compileert een binair bestand waarvan de tabel [[bin]] het aurabasenoemt, niet aura. De gepubliceerde npm-wrapper, @aurabase/cli, toont de twee opdrachten: aura en aurabase verwijzen beide naar hetzelfde script. De naam van een [[bin]] Cargo, de naam van de kist en de naam die zichtbaar is op een npm-wikkelaar zijn drie verschillende dingen; niets kan worden geraden uit de andere twee.

#
Samenvatting

Checklist: waar moet u wat aangeven

Elke keer dat u een krat aan een Cargo-werkruimte toevoegt, komen er vijf beslissingen naar voren. Hier wordt elk gedeclareerd, gebaseerd op het Aurabase-voorbeeld hierboven.

Lijst met leden[werkruimte] ledenRoot - expliciete lijst, nooit een klodder
Gedeelde versie/editie[werkruimte.pakket]Root —version.workspace = true, per krat, optioneel
Afhankelijkheid gedeeld door 2+ kratten[werkruimte.afhankelijkheden]Root — en vervolgens { workspace = true } in elke krat
Afhankelijkheid van één enkele consument[afhankelijkheden] van de kratLokaal, zonder via de werkruimte te gaan
Profiel opbouwen[profiel.release]Alleen root — geldt voor alle leden

Om een bestaande Cargo-werkruimte te controleren (de uwe of die van een project dat u overneemt) zijn vier controles voldoende om het soort discrepanties te vinden dat hierboven is gedocumenteerd:

  1. Vergelijk [workspace.package].version met de version van elke krat. Een andere waarde is niet noodzakelijkerwijs een bug, maar is wel de moeite waard om te documenteren.
  2. Vergelijk [workspace.dependencies] met de afhankelijkheden die lokaal door elke krat worden aangegeven - zoek de namen aan beide kanten met verschillende versies.
  3. Vergelijk de lijst members in de hoofdmap Cargo.toml met de daadwerkelijke submappen in de repository. Een map die ontbreekt in members is niet noodzakelijkerwijs een vergissing.
  4. Controleer de werkelijke naam van elk gecompileerd binair bestand ([[bin]] name) in plaats van aan te nemen dat deze overeenkomt met de kratnaam of de naam die wordt weergegeven door een mogelijke npm-wrapper.

KLAAR VOOR IMPLEMENTATIE?

Uw backend in vijf minuten.

Geen creditcard vereist · 500 MB gratis · 50.000 MAU