Ir al contenido
↑↓Navigate↵SelectescClose

0001 - Rust microservices with axum + tonic for backend

Backend language and framework decision for Nexus services

Esta página aún no está disponible en tu idioma.

This decision sets the backend language for every nexus service that compiles to a binary: gateway, control-plane, auth, policy, ingest, audit, query. The choice is effectively irreversible — changing it later means a rewrite. Constraints:

  • Memory safety is non-negotiable. The gateway terminates TLS and proxies customer prompts; a memory-corruption bug is a cross-tenant data leak.
  • FIPS posture is required (see the forthcoming ADR-0009 for details). Rust is compatible with FIPS-validated crypto modules via aws-lc-rs.
  • The shared-crate contract with Aegis names Rust crates (vortex-types, vortex-classification, vortex-audit-core, vortex-auth-core, vortex-policy-core, vortex-telemetry, vortex-proto). See ../../vortex-common-crates/contracts/shared-crates.md. Choosing a different language for nexus would force re-authoring or FFI-bridging every shared crate.
  • The gateway hot path must be predictable under load; no GC pauses, no runtime warm-up penalty.
  • The Bifrost reference implementation (../reference/bifrost-lessons-learned.md) is Go; the Helicone reference (../reference/helicone-lessons-learned.md) is TypeScript on a Cloudflare Worker. Both shape our informed opinion of what Rust buys us here.
  • Memory safety without GC.
  • FIPS-capable crypto stack.
  • Shared-crate alignment with Aegis.
  • Mature async HTTP (axum) and gRPC (tonic) ecosystems.
  • Predictable latency for the gateway hot path.
  • Strong compile-time checks on wire contracts (tonic-prost-build, sqlx::query!, serde).
  • Rust + axum (HTTP) + tonic (gRPC) — chosen.
  • Go + net/http + grpc-go — Bifrost’s path. Pros: fast compile times, simple onboarding. Cons: GC pauses on the gateway hot path, no compile-time wire checks, shared-crate story requires CGo or parallel implementations.
  • TypeScript on Node — Helicone’s valhalla/jawn path. Pros: shared types with web/. Cons: no memory-safety guarantee for bugs in native modules, GC pauses, FIPS story is immature.
  • TypeScript on Cloudflare Workers — Helicone’s worker/ path. Explicitly rejected because it assumes edge-hosted SaaS, hostile to air-gap deployment (see the Helicone lessons-learned, “patterns to improve on” item 1).
  • Java / Kotlin on JVM — Pros: strong enterprise story. Cons: warm-up latency unacceptable for the gateway, memory footprint heavy for the number of services we run.
  • C++ — No.

Chosen option: Rust with axum for HTTP services and tonic for internal gRPC.

Concrete constraints:

  • MSRV is pinned in the workspace Cargo.toml and updated deliberately (one stable release behind current at any given time).
  • Every service crate lives in services/ (shared library crates in crates/) and depends on the appropriate vortex-* shared crates.
  • Async runtime: tokio with the multi-thread scheduler. No async-std, no mixed runtimes.
  • HTTP server: axum (tower middleware ecosystem).
  • gRPC: tonic server + tonic-prost-build for code generation from .proto under proto/.
  • Database access: sqlx with compile-time query checking against a checked-in schema snapshot (macro query!, not query_as! for raw strings).
  • TLS: rustls with the aws-lc-rs provider (FIPS-capable). No openssl crate dependency on new code.
  • Error handling: thiserror for library errors, anyhow for binaries only; Result<T, E> everywhere.
  • Serialization: serde with serde_json for external JSON; prost for protobuf (via tonic-prost-build).
  • Observability: tracing + the vortex-telemetry shared crate for OTLP wiring (see ../../vortex-common-crates/contracts/telemetry.md).
  • ADRs listed in backlog.md ship with the feature that first needs them: NATS JetStream (ADR-0010), ClickHouse (ADR-0003), OTLP (ADR-0004), BYOK/PTB routing (ADR-0006), audit chain (ADR-0007), classification header (ADR-0008), FIPS build profile (ADR-0009), Next.js console (ADR-0002).
  • A Rust learning tax on contributors not fluent in tokio and lifetimes. We accept this; the onboarding guide (../../CONTRIBUTING.md) links the canonical references.
  • tonic-prost-build and sqlx::query! enforce wire and query correctness at compile time — the inverse of Helicone’s TSOA-generated types (helicone lessons-learned, “patterns to improve on” item 7).
  • aws-lc-rs gives a clear upgrade path to FIPS-validated crypto without swapping TLS stacks per build (to be formalized by ADR-0009).
  • Cross-service contracts live in proto/ and generate both Rust servers/clients (tonic-prost-build) and a TypeScript client used by the Next.js console (buf with the bufbuild/es plugin; see ADR-0002 for the console itself).
  • Build times are non-trivial. We mitigate with sccache in CI and workspace-level incremental builds.