How VRPC works
VRPC makes the classes, instances and functions of one program callable from other programs, as if they were local. You register a class in the program that has it; any other program creates instances of it, calls their functions and receives their events, without an API layer in between: no routes, no schemas to keep in step, no WebSocket plumbing.
This page explains the moving parts. To try VRPC, start with the README; for the exact rules on the wire, see the protocol specification.
Three parties
- The agent runs inside the program that offers code. Its adapter
holds the classes you registered and the instances created from them; the
agent connects that adapter to the broker, receives requests and sends the
answers. In JavaScript these are
VrpcAdapterandVrpcAgent. - The client runs in every program that uses the code - a backend
service, a browser, a command-line tool. It discovers agents, creates and
deletes instances, and hands you proxies: objects whose functions call
the real ones remotely. In JavaScript this is
VrpcClient. - The MQTT broker routes every message. Agents and clients only ever connect out to the broker, never to each other: an agent behind a firewall or a NAT, on a machine without a public address, is reachable as long as it can reach the broker.
A domain groups agents and clients: they see and reach each other within one domain only. An agent has a name, unique in its domain.
What happens on a call
const sensor = await client.getInstance('kitchen', { agent: 'house' })
const value = await sensor.read()
- The client publishes one message to a topic that names the target:
<domain>/house/Sensor/kitchen/read. - The broker delivers it to the agent
house, which subscribed every topic of its classes and instances. - The agent calls
read()on the instancekitchenand publishes the result to a topic of the calling connection. - The client resolves
valuewith it - or rejects with the error the function threw, or with a timeout when no answer came.
Every function call is asynchronous for the caller, whatever the function is. A function that returns a promise is answered once the promise settles. See Calls, callbacks and errors.
Finding things
Agents announce themselves. When an agent serves, it publishes - retained, so every client that connects later gets them too - its agent info (online or offline, its host, its protocol version) and a class info per class (its static and member functions and its shared instances). Clients subscribe to these and always know which agents are online, what they offer and which instances exist:
client.getAvailableAgents()
client.getAvailableClasses({ agent: 'house' })
client.getAvailableInstances({ className: 'Sensor', agent: 'house' })
client.on('instanceNew', (instances, { agent, className }) => { /* ... */ })
When an agent's connection breaks, the broker publishes its last will: the agent info, now offline. Clients learn it at once.
Instances and their lifetime
Clients create instances on an agent and delete them; an instance is either shared (visible to every client) or isolated (it belongs to the connection that created it and goes when that connection goes). An object that runs something - a clock, a socket, a device - releases it when it is deleted. See Instances.
Events
A function that calls a handler again and again - onReading(handler),
on('update', handler) - is an event function. Many clients can listen
to the same events of the same instance; the agent calls the event function
once and hands every event to every subscriber, greets newcomers with the
current state, releases the registration when the last subscriber leaves,
and tells subscribers when it ends. See Events.
Why MQTT
- Outbound connections only. Edge devices, factory machines and browsers all connect out; nothing needs an open port, a reverse proxy or a VPN.
- Built for unstable networks. Connections break and come back; agents and clients reconnect by themselves, and the broker tells everyone when a peer went away.
- Access control at the broker. Every request is a topic that names agent, class, instance and function, so a broker's topic permissions decide who may call what (see the specification's security considerations).
- One broker for everything. Requests, answers, events and presence all travel through it; there is no second channel to secure and operate.
What VRPC is not
- Not a message queue. A request whose answer does not come in time fails; nothing is stored and delivered later. Events that happen while a subscriber is away are not replayed.
- Not a database or a state store. Instances live in the agent's memory. To keep them across agent restarts, use persistence.
- Not a schema-first RPC. There is no interface definition to compile. An agent describes what it offers at run time (its class info, optionally with documentation from your JSDoc), and clients adapt to it.
Implementations
VRPC exists for JavaScript (this repository: Node.js and the browser, with React hooks on top), C++, Python and Arduino. They all speak the same protocol, so a browser can call a C++ agent and a Python service can listen to the events of a Node.js agent. To implement VRPC in another language, see Implementing VRPC.