Getting started
Add the dependency
cargo add velo
cargo add serde --features derive
cargo add tokio --features macros,rt-multi-thread
cargo add anyhow
The default features are http, nats-transport, grpc, and services. Add other features as you need them:
| Feature | What it adds |
|---|---|
services (default) | Distributed events, work queues, and filesystem discovery. See Services and mux-only mode. |
nats-transport (default) | NATS messenger transport |
grpc (default) | gRPC messenger transport and gRPC frame transport |
http (default) | The axum dependency. The HTTP messenger transport is not built at this time. |
zmq | ZeroMQ transport. The build compiles libzmq and needs cmake. |
ucx | UCX transport and RDMA rendezvous. Linux only. See Run rendezvous over RDMA. |
quic | QUIC transport (quinn, TLS 1.3) |
nats-discovery | NATS peer and service discovery |
etcd | etcd peer and service discovery |
nats-queue | NATS JetStream work-queue backend |
queue-messenger | Work-queue backend on active messages |
distributed-tracing | OpenTelemetry trace context in message headers |
simulation | Discrete-event simulation transport |
test-helpers | Prometheus snapshot helpers for tests |
Platforms
velo builds on Linux and macOS. On macOS every feature builds except ucx. There the ucx feature compiles but adds no transport, and the ucx-rs crate builds as an empty stub, so cargo build --workspace --all-features still works. Linux sockets also set TCP_USER_TIMEOUT, which macOS does not have.
TCP is always available. UDS is always available on Unix. Filesystem discovery needs services. Discovery and queue backend features enable services too.
nats-transport, nats-discovery, and nats-queue are independent. They share the async-nats dependency, but one does not enable the others.
A first program
This program connects two instances over TCP and makes one typed request.
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use velo::transports::tcp::TcpTransportBuilder;
use velo::{Handler, TypedContext, Velo};
#[derive(Serialize, Deserialize)]
struct AddRequest { a: i64, b: i64 }
#[derive(Serialize, Deserialize)]
struct AddResponse { sum: i64 }
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let node_a = Velo::builder()
.add_transport(Arc::new(TcpTransportBuilder::new().build()?))
.build()
.await?;
let node_b = Velo::builder()
.add_transport(Arc::new(TcpTransportBuilder::new().build()?))
.build()
.await?;
// Register a handler on node B.
let handler = Handler::typed_unary_async("add", |ctx: TypedContext<AddRequest>| async move {
Ok(AddResponse { sum: ctx.input.a + ctx.input.b })
})
.build();
node_b.register_handler(handler)?;
// Without a discovery backend, register the peer by hand.
node_a.register_peer(node_b.peer_info())?;
let resp: AddResponse = node_a
.typed_unary::<AddResponse>("add")?
.payload(&AddRequest { a: 3, b: 4 })?
.instance(node_b.instance_id())
.send()
.await?;
assert_eq!(resp.sum, 7);
Ok(())
}
Next steps
- Read Messaging for the four request patterns and the handler options.
- Read Streaming to move a sequence of items between workers.
- Read Run the examples to run the examples in the repository.