Introduction
Golem is a durable computing platform that makes it simple to build and deploy highly reliable distributed systems.
The WASM component model eliminates the need for microservice architectures, since components written in different languages can interact with each other in-process (through component composition), without having to go through remote protocols like HTTP, gRPC, or JSON RPC.
Despite this, however, there are times when remote procedure calls (RPC) are useful or necessary when developing applications on Golem:
- You want to parallelize computation that cannot be done on a single worker, either due to lack of memory or lack of compute.
- You want to partition state that is too large to store in a single worker; or, perhaps, you want to partition state that can fit in a single worker, but cannot be read and written fast enough due to contention.
Both of these are examples require the development of a distributed system, where some combination of state and computation is distributed to improve performance, reduce latency, or improve scalability.
To build a system that distributes state or compute, it is necessary to coordinate work, which requires RPC of some kind.
Recognizing the critical nature of internal communication for many distributed systems, Golem provides a feature called worker-to-worker communication. This feature allows you to simply, and in a type-safe way, communicate between workers, with strong guarantees, such as reliable, exactly-once invocation semantics.
WASM-RPC
Prefer the typed approaches to worker-invocation defined above. This section describes the underlying interfaces that are used by golem itself to implement the typed approaches to RPC.
The low-level interface to invoke other workers from within a component is the WASM-RPC (opens in a new tab) wit package.
The basic workflow of using this package is to construct an instance of the wasm-rpc
resource (which represents a remote component worker) and call the appropriate method on it.
Constructing an instance
The only argument required to construct an wasm-rpc resource is the URI of the worker you wish to invoke. The structure of this URI is urn:worker:{component_id}/{worker_name}
.
Both component_id
and worker_name
are given to you by golem when you create a component and worker respectively.
WIT-Value
As the wasm-rpc package is not statically typed, it is required to be able to pass as arguments and return arbitrary WIT values. This is done using the WIT-Value datatype which is a reified
version of the regular WIT types. There is one constructor of the related WIT-Node type for each type in the WIT typesystem, i.e. a list<u64>
with value [1, 2, 4]
might be represented like this in a rust component using wasm-rpc:
WitValue {
nodes: vec![WitNode::ListValue(vec![1, 2, 3]), WitNode::PrimU64(1), WitNode::PrimU64(2), WitNode::PrimU64(4)]
}
The value that will end up becoming the root of the resulting WIT Value needs to be placed in index 0 of the nodes array!
All functions in the wasm-rpc package use wit-value instead of the equivalent wit type. Invoking a function with incorrect wit-values with lead to an error.
Invocation
After you have constructed an instance of wasm-rpc there are a number of different functions you can choose from, depending on the invocation semantics you need.
- invoke: Non-blockingly call the desired function. Errors during invocation are returned, but the actual result of the invocation cannot be accessed.
- invoke-and-await: Blockingly call the desired function. The result of the invocation will be returned to you.
- async-invoke-and-await: Non-blockingly call the desired function. A resource will be returned to you that you can use to poll the result.
- schedule-invocation: Schedule an invocation for a point in time in the future.
- schedule-cancelable-invocation: Schedule an invocation for a point in time in the future. A resource will be returned to you that you can cancel the invocation as long as it hasn't been executed yet.
Example
Given the following WIT world implemented by a component:
package golem:example;
interface invocation-example-api {
add: func(value: u64);
}
world invocation-example {
export invocation-example-api;
}
A rust component could use the wasm-rpc package to schedule an invocation of 'add' 2 seconds in the future like this:
let uri = Uri { value: format!("urn:worker:{}/{}", component_id, worker_name) }
let wasi_rpc = WasmRpc::new(&uri);
let now = OffsetDateTime::now_utc();
let scheduled_time = now.saturating_add(Duration::seconds(2));
let scheduled_wasi_time = WasiDatetime {
seconds: scheduled_time.unix_timestamp() as u64,
nanoseconds: scheduled_time.nanosecond()
};
let value: WitValue = {
use self::bindings::golem::rpc::types::*;
WitValue {
nodes: vec![WitNode::PrimU64(1)]
}
};
wasi_rpc.schedule_invocation(scheduled_wasi_time, "golem:example/invocation-example-api.{add}()", &vec![value]);