Le cœur Rust d’Aurabase : architecture d’un Backend-as-a-Service unifié
Aurabase est un Backend-as-a-Service écrit en Rust — pas seulement pour quelques services périphériques, mais pour l’intégralité de son cœur applicatif : passerelle, authentification, base de données, temps réel, stockage, notifications, IA, provisioning. Le workspace Cargo à la racine du dépôt liste 18 crates compilées ensemble par un seul cargo build --workspace, sans langage tiers caché derrière la logique produit.
Ce dossier documente cette architecture telle qu’elle existe réellement dans le code, vérifiée fichier par fichier au 23 août 2026 — schémas, chiffres et extraits inclus. C’est la page pilier du cluster « Ingénierie Rust » : elle donne la vue d’ensemble, et renvoie vers les analyses techniques approfondies (Axum, Cargo workspace, passerelle, PostgREST, pg_graphql, et le reste du dossier) publiées ou en cours de publication. Pour la comparaison produit complète face à un BaaS concurrent, voir notre comparatif Aurabase vs Supabase.
- Workspace Cargo unique : 18 crates (11 services métier, la CLI
aura, 5 bibliothèques partagées, le SDK Rust) compilées ensemble par un seulcargo build --workspace. - Le code métier pèse environ 274 000 lignes de Rust (mesuré via
find+wc -l, 23 août 2026), réparties sur ces 18 crates. - La passerelle (
aura-gateway) sépare deux plans — données (SDK, port 8080) et gestion (Studio, port 8090) — chacun avec son propre empilement de middlewares et sa propre authentification. - Aurabase ne réécrit pas PostgREST : le vrai binaire amont (v12.2.8) tourne par tenant, orchestré par des services Rust — la valeur ajoutée est autour, pas à la place.
- Seule entorse notable au Rust pur : le runtime par défaut des Edge Functions (mode
deno) est un service TypeScript dédié à base de V8 Isolates ; un second chemin, natif et en Rust via Wasmtime, existe pour le modewasm.
Ce qui distingue Aurabase d’un BaaS assemblé service par service
La plupart des BaaS Postgres open source assemblent leurs services dans plusieurs langages. Ce n’est pas un jugement de valeur — c’est un fait d’architecture qui a des conséquences concrètes : autant de chaînes de compilation, de conventions d’erreurs et de logiques d’auth à maintenir en synchronisation qu’il y a de langages en jeu.
Chez Aurabase, la couche produit qu’on écrit et maintient nous-mêmes — passerelle, auth, base de données, temps réel, stockage, notifications, IA, provisioning, gestion des comptes — est un seul workspace Cargo, un seul langage, une seule chaîne de build. C’est le choix que documente ce dossier.
18 crates, une seule chaîne de compilation
Le Cargo.toml racine déclare un workspace Cargo en resolver v2 avec 18 membres : 11 services métier, la CLI aura-cli, 5 bibliothèques partagées, et le SDK aurabase-rs. Voici la liste réelle, telle qu’elle apparaît dans le dépôt.
Les dépendances partagées vivent dans [workspace.dependencies] : Axum 0.8 (avec WebSockets, multipart, macros), Tokio, Tower/Tower-HTTP, SQLx 0.8 (Postgres + MongoDB via mongodb pour le moteur NoSQL secondaire), async-nats 0.47, sqlparser 0.53 (validation SQL du NL2SQL), oauth2 5, jsonwebtoken 10, et deux libs de performance qu’on retrouve dans presque chaque service : mimalloc comme allocateur global et moka/dashmap pour le cache en mémoire.
Le profil de release documente un choix assumé : panic = "unwind" plutôt que "abort". Le commentaire du fichier est explicite — un panic dans un handler Axum/Tokio est isolé par le runtime (la requête concernée renvoie 500) au lieu de faire tomber tout le processus et couper les requêtes concurrentes. Le gain de perf d’abort (1 à 2 % de RPS environ) ne vaut pas la perte d’isolation, d’après cette même note. C’est un compromis fiabilité-vs-vitesse documenté dans le code, pas une affirmation marketing.
Un seul cargo build --workspace compile l’ensemble. Un seul cargo test --workspace fait tourner toute la suite de tests. Un seul cargo clippy --workspace --all-targets -- -D warnings lint tout le produit avec les mêmes règles. Le détail de cette structure — héritage des dépendances, graphe interne entre libs et services, pièges qu’on a rencontrés en la faisant grandir — fait l’objet d’un article dédié : architecture Cargo workspace, comment structurer un backend Rust multi-services.
Lignes de Rust par service (find services -name '*.rs' | xargs wc -l, 23 août 2026) :
Hors libs partagées (aura-db-adapters : 24 725 lignes, aura-core : 7 259, aura-migrations : 3 641, aura-crypto : 2 718, aura-telemetry : 201), la CLI (aura-cli : 9 845) et le SDK Rust (aurabase-rs : 6 363). aura-migrator, un job de migration à usage unique et non un serveur HTTP long-lived, reste volontairement le plus petit service du workspace.
11 services métier, chacun un serveur Axum autonome
Chaque service est un binaire Axum/Tokio indépendant, avec sa propre configuration et son propre port. Dix des onze exposent /health et /metrics et déclarent mimalloc comme allocateur global — la seule exception, aura-migrator, est un job à usage unique plutôt qu’un serveur qui tourne en continu.
La CLI aura (≈ 9 800 lignes) parle aux mêmes API que les SDK — elle n’a pas de chemin privilégié. La référence complète de chaque service vit dans la documentation d’architecture et la référence CLI.
5 crates qui évitent la dérive inter-services
Dans un stack polyglotte, une règle de sécurité — le format d’erreur, la protection SSRF, le rate limiting — doit être réimplémentée dans chaque langage, et dérive presque toujours au fil des mois. Aurabase la code une fois, dans une lib du workspace, consommée par tous les services concernés.
Conséquence directe : un correctif de sécurité dans aura-core — la protection SSRF, par exemple — se propage à chaque service consommateur au prochain cargo build, pas via cinq correctifs séparés dans cinq langages.
Double plan : trafic SDK contre trafic Studio
aura-gateway sépare deux surfaces qui n’ont ni les mêmes clients, ni le même modèle d’auth. Le plan de données (port 8080, variable GATEWAY_PORT) reçoit le trafic SDK/app, authentifié par clé API (apikey, X-API-Key ou ?apikey=). Le plan de gestion (port 8090, MANAGEMENT_PORT) reçoit le trafic Studio/admin, authentifié par un JWT console (Authorization: Bearer).
Chaque plan porte son propre empilement de middlewares — identification de requête, journal d’accès, limite de débit, authentification (spécifique au plan), disjoncteur, puis proxy — implémentés dans des modules séparés (middleware/rate_limit.rs, middleware/circuit_breaker.rs, middleware/auth_data_plane.rs, middleware/auth_management_plane.rs) plutôt qu’une seule chaîne partagée par accident entre deux surfaces qui ne devraient pas se faire confiance de la même façon.
L’ordre exact des middlewares, le rate limiting par cible et le proxy NATS/HTTP font l’objet d’un article dédié : passerelle à double plan, concevoir un API gateway data-plane/management-plane en Rust.
Pourquoi Aurabase ne réimplémente pas PostgREST en Rust
L’API REST auto-générée que consomme le SDK n’est pas un module Rust maison : c’est le vrai binaire amont PostgREST (postgrest/postgrest:v12.2.8), déployé en 2 réplicas par projet dédié (deploy/cnpg/tenant-postgrest.yaml), co-localisé avec l’instance CNPG du tenant. aura-provisioner crée ce déploiement, et aura-db sonde son endpoint OPTIONS après chaque reload de schéma pour confirmer que PostgREST a bien pris en compte le changement de DDL.
C’est un choix d’architecture assumé, pas un raccourci : PostgREST est un projet mature, largement adopté, dont réécrire le comportement au bit près en Rust n’apporterait rien. Le travail Rust d’Aurabase se concentre autour — routage multi-tenant, provisioning, isolation réseau par NetworkPolicy, auth partagée avec le gateway, reload de schéma orchestré — pas à l’intérieur du moteur de requêtes lui-même. Ce que cette compatibilité couvre vraiment, et où elle s’arrête, fait l’objet d’un article séparé : PostgREST, compatibilité réelle et alternatives.
Même logique côté GraphQL : l’extension Postgres pg_graphql (paquet .deb précompilé, v1.6.1) est installée dans l’image CNPG dédiée et activée à la demande par projet via l’endpoint POST /v1/control/projects/{project_id}/graphql/enable — pas un moteur GraphQL réécrit non plus. Détails et comparatif honnête face à Hasura et PostGraphile : API GraphQL native sur Postgres avec pg_graphql.
RLS et rôle authenticator, pas une couche applicative
L’isolation multi-tenant ne repose pas sur un filtre WHERE tenant_id = ? ajouté par un ORM applicatif : chaque requête passe par le rôle aura_authenticator, qui exécute un SET LOCAL ROLE tenant_<uuid> transaction-scoped avant d’exécuter la requête — exactement le modèle qu’attend PostgREST lui-même. Row-Level Security fait le reste, au niveau moteur, pas au niveau code métier.
Les projets ne partagent pas tous la même topologie Postgres. Le code de aura-provisioner expose un type ProjectInstanceKind avec au moins deux variantes réelles : FullyDedicated (instance CNPG entièrement dédiée au projet) et SharedClusterDedicated (schéma isolé par RLS sur un cluster CNPG mutualisé). Le plan souscrit détermine la topologie — ce n’est pas une promesse uniforme de « base dédiée pour tout le monde ».
NATS cœur, pas JetStream : la messagerie asynchrone entre services
Dix des onze services métier déclarent async-nats en dépendance directe dans leur Cargo.toml — seul aura-migrator s’en passe. Ce que ce nom de crate ne dit pas : ces services utilisent presque partout l’API pub/sub cœur de NATS (Client::publish / publish_with_headers, livraison at-most-once sans persistance ni relecture), pas JetStream. C’est le cas vérifié dans le code pour les trois usages qui comptent le plus : la diffusion du CDC PostgreSQL vers aura-realtime, la notification de jobs des Edge Functions dans aura-functions (dont la DLQ elle-même vit en Postgres, pas dans NATS), et les événements de provisioning entre aura-provisioner et le reste de la flotte.
JetStream — le mode avec persistance et streams nommés de NATS — n’apparaît qu’à un seul endroit vérifié du monorepo : le KV Store de présence cross-instance dans aura-realtime (bucket aura_presence, stockage mémoire, max_age de 60 secondes), qui synchronise qui est connecté à quel canal entre plusieurs instances de ws-front — un état partagé cross-instance, pas un flux d’événements à rejouer. Aucun stream JetStream persistant n’a été trouvé ailleurs dans le workspace. Le détail du pipeline CDC — wal2json, élection d’un cdc-worker unique par Lease Kubernetes, fan-out NATS cœur vers les réplicas ws-front, revérification RLS par abonné — fait l’objet d’un article dédié : diffuser le CDC PostgreSQL avec NATS. La documentation Realtime couvre l’usage côté SDK.
Edge Functions : deux runtimes pour deux besoins
C’est la nuance la plus importante de ce dossier, et la seule véritable entorse au Rust pur dans le code produit d’Aurabase. Chaque fonction porte un champ runtime qui vaut "wasm" ou "deno". Le handler d’invocation choisit le chemin d’exécution en conséquence — extrait réel, commenté dans le code lui-même :
Le mode wasm est natif : aura-functions dépend directement de Wasmtime (version 43, fonctionnalités async et cranelift) et exécute le module dans le même process Rust, avec comptage de fuel, interruption par epoch et bornage mémoire via StoreLimits. Le mode deno — celui utilisé par défaut dans l’éditeur du Studio, pour une compatibilité Deno.serve() quasi directe avec du code Supabase existant — délègue l’exécution à aura-edge-runtime, un service TypeScript distinct d’environ 550 lignes, explicitement modelé — le commentaire d’en-tête du fichier source le cite lui-même — sur supabase/edge-runtime (licence MIT), qui isole chaque invocation dans son propre V8 Isolate.
aura-functions. Seule l’exécution du code utilisateur en mode deno sort du binaire Rust. C’est un compromis d’ingénierie défendable (les V8 Isolates sont ce que Deno fournit nativement pour ce niveau de sandboxing), pas une omission qu’on préfère taire. Le comparatif Wasmtime vs Wasmer et l’analyse cold start WebAssembly, à venir dans ce dossier, iront plus loin sur ce sujet.Pour la doc pratique des deux chemins de déploiement, voir Edge Functions. Pour le playbook de migration Deno depuis Supabase, voir migrer un projet Supabase vers Aurabase, qui documentait déjà cette distinction avant ce dossier.
Ce que ce cœur unifié change concrètement pour vous
Si vous ne faites qu’appeler l’API via un SDK, cette architecture est invisible — c’est le but. Elle compte surtout pour trois audiences : ceux qui évaluent la fiabilité opérationnelle d’un backend multi-tenant avant d’y migrer des données de production, ceux qui envisagent de contribuer au dépôt (MIT, monorepo unique), et ceux qui veulent comprendre pourquoi un correctif de sécurité côté Aurabase se propage vite plutôt que lentement.
Concrètement : une seule CI (cargo build --workspace, cargo test --workspace, cargo clippy --workspace --all-targets -- -D warnings) couvre 90 % et plus de la surface produit. Une revue de code sur aura-core touche potentiellement dix services d’un coup — pour le meilleur (un correctif ne se perd pas en route) comme pour le pire (un changement mal isolé se propage aussi vite). C’est un compromis, pas une solution magique — et c’est précisément pour ça qu’on documente l’architecture réelle plutôt qu’un résumé marketing.
La suite du dossier Ingénierie Rust
Cette page pilier renvoie vers les analyses techniques du cluster, au fil de leur publication. Statut réel au moment de la publication de cette page — les liens s’activent dès que l’article correspondant est en ligne.