/architecture/ · The Harness core
Ports and adapters, composed at compile time.
Harness is a set of Rust crates. Modules ask for ports. Runtimes supply adapters. The builder checks the match before anything runs, and the compiler enforces that modules never see a vendor.
The module contract · Shipping
Four methods.
A module declares its name, the ports it requires, its migrations, and an axum router. Nothing else. Harness::build() refuses a module that requires a port the runtime lacks, two modules on the same route prefix or table, or a module built against another contract version. That failure happens in cargo test.
pub trait Module: Send + Sync + 'static { fn name(&self) -> &'static str; // mounted at /v1/<name> fn requires(&self) -> &'static [Port]; // build fails if one is missing fn migrations(&self) -> Migrations; // include_str! SQL, portable subset fn router(&self, ctx: ModuleContext) -> axum::Router; }
Ports
Ten trait objects. No vendor in sight.
CI proves the core stays vendor-free by building the example backend to wasm32 every commit. A dependency pulling tokio, mio or std::fs fails the build.
Request lifecycle · Shipping
Why Rust
The compiler is the test you cannot skip.
Rust is not on this stack because it is fast in a benchmark. It is here because a whole class of production incident becomes a build failure, and because a backend with no garbage collector has no collection pause to schedule around: latency is what your code does, not what the runtime decided to do that second. What ships is one compiled module, not an interpreter and a dependency tree.
The table below compares language guarantees, not speed. Every row is something you can check in a compiler.
| Property | TypeScript on Node | Python | Go | Rust |
|---|---|---|---|---|
| Memory management | Garbage collected | Reference counting plus a cycle collector | Concurrent garbage collector | Ownership and borrowing. Memory is released at the end of scope, by the compiler |
| Collection pauses | Yes | Yes | Short, concurrent | None. There is no collector to pause |
| Memory footprint | Heap headroom is reserved for the collector | Heap headroom is reserved for the collector | Heap headroom is reserved for the collector | What you allocate, for as long as you hold it |
| Absence of a value | null and undefined, at runtime | None, at runtime | Zero values and nil; misuse panics at runtime | Option<T>. Absence is in the type, and the compiler makes you handle it |
| Errors | Exceptions, unchecked | Exceptions, unchecked | Explicit error returns, but ignoring one compiles | Result<T, E>. Ignoring one is a warning, and clippy -D warnings makes it a build failure |
| Data races | Single-threaded event loop; shared memory across workers is hand-managed | The GIL serialises bytecode | Possible; found at runtime by the race detector | Send and Sync are checked at compile time. A data race in safe code does not build |
| Use after free, buffer overrun | Prevented by the runtime | Prevented by the runtime | Prevented by the runtime | Prevented by the compiler, and #![forbid(unsafe_code)] means a crate cannot opt out |
| What gets deployed | The runtime plus node_modules | The interpreter plus site-packages | A static binary | One compiled module |
| Where a mistake surfaces | Runtime | Runtime | Mostly runtime | Compile time |
No benchmark appears here. We have not run one, and quoting someone else's would not make it ours. Each of these languages is a reasonable choice for a backend, and the ones with a garbage collector are easier to hire for; what Rust buys is that the composition rules on this page are enforced rather than documented.
Rules · Shipping
Stateless by construction.
Rust compiled to WebAssembly on Cloudflare Workers via workers-rs and axum. No static mut, no thread-local outliving a request. Request scope travels in axum extensions, and the conformance kit ships the concurrent-request test that proves it. Migrations are forward-only, SQL in a portable subset that runs on SQLite today and Postgres when the adapter lands.
- RFC 9457 application/problem+json errors with stable type URIs
- HMAC-signed links with kid rotation and constant-time compare, so there is no session store
- No account enumeration: identical 202 for every state
- Rate limits on every public endpoint
- Admin endpoints off entirely when the token is unset
- CSV exports escape formula injection
- Secrets redacted from logs; emails logged only as a truncated hash
- PII minimalism: no IP or user-agent stored
- One structured span per request, x-request-id on every response
Every snippet on this page is in the repository. Wrong Rust here would be fatal, so there is none invented.