Skip to content

0001 - Rust microservices with axum + tonic for backend

  • Status: Accepted
  • Date: 2026-04-19
  • Deciders: nexus core team
  • Tags: language, framework, architecture, backend
  • Consulted: Aegis core team (shared-crate owner; see vortex-common-crates/contracts/shared-crates.md)

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 is Go; the Helicone reference 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 the backlog 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 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.