Practical Ways To Use WebAssembly Beyond the Browser
From severs to tiny devices that beep
If you aren't running demanding browser applications, using WebAssembly at all may not seem to make sense to you. WebAssembly was indeed intended to run inside the browser, however, since the specification makes no assumptions that are specific to the browser, some stand-alone execution environments have emerged to run Wasm on other platforms aside from the browser like embedded devices, other host languages, web servers and even in Kubernetes clusters and I have in the past written about why all of these make sense.
Despite the benefits, it may still be difficult to get started and find a way to fit WebAssembly into your tech stack. Having experimented with various runtimes and use cases, I want to share the practical ways (I know) you can start using Wasm on the backend.
1. Deploying (Tiny) Wasm Services
This method works by having some Wasm modules on a host machine and a gateway program that sits between the machine and the outside world.
The gateway program could also embed a Wasm runtime. It exposes an HTTP endpoint and listens for incoming requests. When a request is received, it reviews it and loads the Wasm module that serves the function the client needs. As for the results of the operation (if any); if the gateway program embeds a Wasm runtime, they are passed in-memory from the module, returned to the client and the module is off-loaded.
Pros
Flexibility: Since everything is broken down into Wasm modules, you can deploy new modules and even upgrade existing ones without making changes to the whole stack.
Performance: Every module runs at Wasm's promised near-native performance but this of course depends on the runtime you choose.
Isolation: All your modules run alone, they consume resources alone and can thereby be inspected alone. This is similar to what containers offer.
How long does it take you to spin out a VM with a container instance? Now cut that by 80%. Wasm modules are lightweight and portable.
Cons
- It is still very much experimental and things may break -- a lot.
This should encourage early adopters to raise issues and drive the speed of development and bug fixing.
Use case
Anything you can achieve with a microservices-based backend, you can also achieve with this setup: remote API calls, user data access and many more. You can even manage your modules with Kubernetes -- and with the improvements that are being made to WASI, Wasm modules are about to get a lot more powerful while still keeping their promise of safety.
The (experimental) Spin Framework is built specifically for this use case. Stay tuned for future posts on how to practically implement something like this.
2. Embedding Wasm Runtimes
You can take a regular programming language like Java or Python, and then invoke a library that encapsulates all the Wasm facilities. This library ideally exposes an API that enables you to create an instance of a Wasm VM and from there you can invoke the VM anytime to load and execute a module. The fascinating part of this is that the modules will run at near-native speed even if the host language does not ordinarily support that.
Take a look at how we can load a .wasm
file from a Python program using the wasmer
runtime
from wasmer import engine, Store, Module, Instance
from wasmer_compiler_cranelift import Compiler
# Let's define the store, that holds the engine, that holds the compiler.
store = Store(engine.JIT(Compiler))
# Let's compile and instantiate the module.
module = Module(store, open('my_program.wasm', 'rb').read())
instance = Instance(module)
# Call the exported `sum` function naturally.
result = instance.exports.sum(5, 37)
assert result == 42
Use case
If a language can be compiled to Wasm, then modules can be created to be loaded by the host.
There are two ways to achieve this:
The client could send the source code to the server, and then the server could initiate a routine that compiles the code to Wasm binaries before it's loaded and executed by the host.
The client could also compile the source to Wasm binaries locally, and then send the binaries to the Wasm host which still achieves the same goal.
Whichever way, the modules run in a sandbox environment that prevents them from doing anything malicious. They also run at near-native performance regardless of the host language. I see two use cases for this:
Function-as-a-Service: Where users can create a function that should be executed on request. These functions can be compiled in Wasm modules as described earlier.
On-the-fly Re-programming (or a better name): Embedded devices that exist in a remote location can receive quick and safe updates/configurations without having to physically have them attached to a dedicated programming module. WasmEdge is built for these kinds of use cases.