WebAssembly Microservices Architecture
WebAssembly gives microservices a portable, sandboxed execution boundary that works across browser, edge, and cloud. This page explains what that means for architecture, what WASI adds, and where the pattern fits.
What WebAssembly is
WebAssembly (WASM) is a binary instruction format designed as a compilation target, not a language you write by hand. The W3C standardized it in 2019. Compilers for Rust, Go, C, C++, and TypeScript (via AssemblyScript) can emit WASM modules that run inside a sandboxed virtual machine with near-native performance.
The execution model is deterministic: the same WASM module, given the same inputs, produces the same outputs regardless of host OS, CPU architecture, or embedding context. That determinism is not an implementation detail: it is part of the specification. This is what makes WASM interesting outside the browser.
The virtual machine is a stack machine. The binary format is compact and fast to parse. The security model is capability-based by default: a WASM module cannot access the host's memory, file system, network, or any other resource unless the host explicitly grants each capability at startup. This is the opposite of most process-level isolation, where everything is accessible and you restrict by policy after the fact.
Why WASM fits microservices
Microservices architectures have long had a mismatch between their stated goal (isolated, independently deployable units) and their reality (shared OS, shared language runtime, ambient environment access). A Node.js service and a Python service on the same Kubernetes node share more than their network namespace. WASM closes some of that gap.
Four properties make WASM worth considering for service boundaries:
- Near-native performance. WASM modules are ahead-of-time compiled to machine code by the host runtime. Cold start times are measured in milliseconds, not seconds. This matters at the edge where container cold starts are prohibitive.
- Capability-based security sandbox. The module has no ambient authority. It cannot open a file, make a network connection, or read an environment variable unless the host passes it that capability explicitly. This makes the permission surface explicit and auditable rather than implicit and assumed.
- Language-agnostic compilation target. Teams are not forced onto a single language stack. A Rust module and a Go module can run in the same WASM host. Interoperability is handled at the binary format level, not at the language level.
- Consistent behavior across runtimes. The same module runs in a browser's WASM API, a Cloudflare Worker, and a wasmtime process on a Linux server. The host surface differs, but the module's behavior does not. This is a stronger portability guarantee than container images provide.
WASI: the system interface
WASM alone has no I/O. A pure WASM module cannot read a file, make an HTTP request, or even get the current time. This is intentional (the sandbox is explicit) but it means server-side WASM requires a standardized interface to host capabilities.
WASI (WebAssembly System Interface) is that interface. It defines a set of portable, capability-gated APIs for clocks, file descriptors, network sockets, environment variables, random number generation, and (as of WASI 0.2) HTTP. Modules import WASI functions the same way they import any other external function. The host decides which WASI capabilities to provide at startup.
WASI 0.2 reached stable status in February 2024. It introduced the Component Model as its foundation (discussed below) and added wasi:http: a standardized interface for outbound HTTP requests and inbound HTTP handlers. This is the release that made server-side WASM practical for real service workloads, not just compute-heavy tasks.
WASI 0.1 (the "Preview 1" release) is still widely used. Many toolchains default to it. The two versions are not binary-compatible. a module compiled for WASI 0.1 needs a shim to run in a WASI 0.2 host, and vice versa. This is a real operational concern when choosing a toolchain today.
The component model
A core WASM module is a black box: bytes in, bytes out, with no typed interface beyond function signatures expressed in WASM value types (i32, i64, f32, f64, and a handful of reference types). This is fine for simple compute tasks but insufficient for composing services with rich data contracts.
The WASM Component Model, stabilized alongside WASI 0.2, adds a typed interface layer on top of core modules. Components define their imports and exports using WIT (WebAssembly Interface Types), a description language that supports records, variants, options, lists, resources, and streams. enough expressiveness to describe real service contracts without inventing your own serialization format.
Two components that agree on a WIT interface can be composed: the output of one becomes the input of the other without going through a serialization boundary. This is structural composition at the binary level, not integration via HTTP or gRPC. It is closer to linking than to calling.
For microservices, the Component Model means the contract is machine-readable, version-aware, and embedded in the artifact itself. A runtime can inspect a component's WIT interface without running it: the same property that makes type systems useful at build time applies here at deployment time.
How UMA uses WebAssembly
UMA packages portable business logic as WASM modules. The module contains the durable behavior: the logic that must produce identical results regardless of where it runs. The runtime layer decides where that module executes and supplies the capabilities the module needs.
The same compiled module runs without recompilation in three contexts:
- Browser: via the browser's native WASM API. The host is the JavaScript engine. WASI capabilities are polyfilled or omitted depending on what the browser surface supports.
- Edge: via Cloudflare Workers or Fastly Compute. These runtimes implement a subset of WASI 0.2 including
wasi:http. Cold starts are sub-millisecond because WASM modules are pre-compiled to V8 snapshots or equivalent.
- Server: via wasmtime or WasmEdge running inside a container or directly on a host. Full WASI 0.2 support including file I/O and network access.
What UMA adds on top of WASM is the contract layer. A WASM module is an execution boundary. UMA's active descriptors make that boundary explicit in terms the runtime can evaluate: input schema, output schema, allowed placements, version constraints, and evidence expectations. The module provides the portability. the descriptor provides the governance.
What WASM doesn't solve
WASM is an execution boundary, not an architecture. It does not define how services discover each other, how contracts evolve across versions, how placement decisions are made, or how policy is enforced at runtime. These are architecture concerns, not VM concerns.
Specifically, WASM and WASI do not provide:
- Service contracts. WIT describes the interface of a single component. It does not describe the behavioral contract across a workflow. what events are emitted, what latency is expected, what evidence a run should produce.
- Runtime placement decisions. Nothing in the WASM or WASI specification tells a runtime where a module should execute. That decision requires knowledge of the execution environment, the service's requirements, and the policy in force at deployment time.
- Governance. WASM's sandbox prevents unauthorized resource access inside the module. It does not enforce who can deploy the module, which versions are compatible, or what the audit record for a run should look like.
These are exactly the concerns UMA's runtime layer and active descriptors address. WASM is the mechanism that makes the execution boundary cheap and portable. UMA is the model that makes the boundary meaningful in the context of a real system.
Current runtime landscape
The WASM server-side runtime ecosystem is stable enough for production use in specific categories and still maturing in others.
- wasmtime (Bytecode Alliance): the reference WASI 0.2 implementation. Production-grade, actively maintained, used as the embedded runtime in several commercial products. Rust API is the most complete. C, C++, and Go bindings exist.
- WasmEdge (CNCF project). strong focus on edge and cloud-native deployments. Supports WASI 0.1 and has experimental WASI 0.2 support. Used in production at several large-scale edge deployments. Better Go integration than wasmtime in practice.
- Cloudflare Workers: V8-based, WASI 0.2 compatible as of 2024. Production-ready for HTTP-oriented workloads. The largest deployed base of server-side WASM today, by volume.
- Fastly Compute: wasmtime-based. Mature WASI 0.1 support. WASI 0.2 in progress.
- Browser WASM API: the original context. Fully stable. No WASI support natively. host glue is required for anything beyond pure compute.
Honest maturity assessment: WASM at the edge is production-ready. WASM for general server-side microservices replacing containers is maturing. toolchain ergonomics, debuggability, and WASI 0.2 adoption across runtimes are the current friction points. The trajectory is clear. the timeline depends on toolchain investment from the major runtimes through 2025–2026.
Questions and answers
- Do I need to write in Rust to use WASM?
- No. Rust has the most mature WASM toolchain and the smallest output binaries, which is why most WASM examples use it. But Go (via TinyGo for WASI 0.1 or the upstream Go compiler's WASI 0.2 target added in Go 1.21+), TypeScript (via AssemblyScript), C, and C++ all compile to WASM with varying degrees of WASI support. The tradeoffs are: binary size (Rust and C are smallest), garbage collection overhead (Go includes a GC. this increases module size and affects cold start), and toolchain maturity (Rust WASI 0.2 is the most complete as of mid-2024).
- Is WASM production-ready for microservices?
- It depends on the deployment target. For edge and serverless workloads (Cloudflare Workers, Fastly), yes. WASM is the execution model, not an experiment. For server-side microservices replacing containers in Kubernetes, the answer is "production-capable for the right workloads, maturing for the general case." Stateless, compute-heavy, or latency-sensitive services are the best fit today. Services with complex filesystem or network requirements should evaluate carefully against the WASI 0.2 capability surface of their chosen runtime.
- Does WASM replace containers?
- For some workloads, yes. WASM modules are faster to start, smaller to distribute, and more strongly isolated per-module than containers. For workloads that depend on the full Linux userspace, existing OS tooling, or mature language runtimes that do not compile to WASM, containers remain the practical choice. The two models coexist in most realistic architectures.
WASM strengths
Deterministic execution. Capability-based sandbox with no ambient authority. Near-native performance with sub-millisecond cold starts at the edge. Language-agnostic: Rust, Go, C, TypeScript all target WASM. Portable binary. compile once, run in browser, edge, and server without recompilation.
WASM current limits
WASI 0.2 adoption is uneven across runtimes as of 2024. Debugging WASM in production is harder than debugging native processes. Toolchain ergonomics vary significantly by source language. GC-based languages (Go, Kotlin) produce larger modules with GC overhead. Not a substitute for architecture. no native service discovery, contract governance, or placement policy.
Where this fits in UMA
WASM provides the execution boundary. UMA's active descriptors and runtime layer provide the contract, governance, and placement logic that turn that boundary into a managed service. The two are complementary, not redundant.