Skip to main content

RPC

View as Markdown

An RPC (Remote Procedure Call) lets code outside a Flow interact with a running Flow. A web handler, another service, or an operator tool calls it through a Client.

A Client invokes an RPC. Dex Server sends it to a Worker. The Worker runs it, then returns the result to Dex Server, which saves it. Dex Server then returns the output to the Client.

When to use an RPC​

Use an RPC when something outside the Flow must change it or ask it a question. Examples are an approval, a user who presses Stop, or a request for a view of the Flow's state.

An RPC handler can do four things:

For a plain read, a Client can skip the RPC. See when a read needs an RPC. For RPCs that only read, including on closed Flows, see Read-only RPCs and terminal Flows.

Guarantees​

Dex saves an RPC like a Step method. It saves the RPC's Attribute writes and Channel publications together with its result, and only when the RPC succeeds. See Step guarantees.

An RPC handler reads a snapshot of the Flow. Steps and other RPCs can change the Flow between that read and the moment Dex saves the RPC's changes. A transactional RPC does not change this. When the RPC's change depends on what it read, declare a lock on that Attribute in this RPC and in every Step and RPC that writes it. Dex then runs them one at a time. If the Flow closes while the handler runs, Dex rejects the RPC's changes. The handler's calls to external systems have still happened.

Each RPC call from an SDK is a new request. If you retry a call, Dex can run the handler again and save its changes a second time. Make RPC handlers safe to repeat.

An RPC runs on a Worker, like a Step. A timeout limits each handler invocation. See RPC timeout.

Define and invoke an RPC​

This RPC writes an Attribute, publishes a Channel message, and starts a Step:

rpcPython
@rpc
def trigger(self, context: Context, input: str) -> RPCResult[str]:
self.data.set(context, input)
self.example_ch.publish(context, None)
return RPCResult(input, next_steps=(StepMovement.of(Example, input),))

Example: examples/python/dex_examples/primitives/rpc

Then invoke the RPC from a Client:

rpcPython
result = await client.invoke_rpc(flow.trigger, flow_id, message)

Example: examples/python/dex_examples/primitives/rpc

Java, Kotlin, and TypeScript have extra RPC rules. See RPC stubs and Kotlin and RPC codecs.

Cancel running Steps​

An RPC result can cancel active Steps of its Flow, selected by Step type. Use it when a request from outside makes work in progress pointless, such as a user who presses Stop while a report or a model call runs. Dex applies the cancellation when it saves the RPC's result. The Step code must check for cancellation and stop. See Cancel running Steps for the example, Resolve the race from committed state for a Step that finishes first, and Make the Step stop for how each SDK delivers cancellation.

Locks and transactional RPCs​

An RPC can declare Attribute locks. It then runs one at a time with the Steps and RPCs that declare the same lock. See Locks and RPC lock conflicts.

A transactional RPC adds a check when Dex saves its result. If a Channel message that it deletes is no longer pending, the RPC fails and Dex saves none of its changes. A non-transactional RPC skips the missing deletion and Dex saves the rest. An RPC that holds a lock is always transactional. See Transactional RPCs.

Load collections​

An RPC receives regular Attributes and Channel sizes. It loads AttributeMap entries and pending Channel messages only when it declares them. This keeps large collections out of RPCs that do not use them. See Selective state loading and Runtime-selected map instances.

Reference​

The RPC page of each SDK reference lists the API names in that language.

Data primitive semantics covers cancellation, read-only RPCs on closed Flows, state loading, transactional RPCs, and timeouts in detail.