RPC
RPC 是 “Remote Procedure Call” 的缩写,允许外部系统与 Flow execution 交互。
Client 发起 RPC。Dex Server 将它发送给 Worker。Worker 执行后,将结果返回给 Dex Server 持久化。Dex Server 再将输出返回给 Client。
定义和调用 RPC
RPC 可以通过 Attribute 读取和写入 durable state、向 Channel publish,也可以 trigger 一个全新的 Step 来运行。
每次 RPC 调用都是一个 commit 边界。Dex Server 会暂存 RPC 中的 Attribute 写入和 Channel publish,并只在 method 成功返回时,将它们与 RPC result 一起 commit。如果 RPC 失败,这些 durable 变更都不会被 commit。外部 API 调用不在这个原子边界内。
@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),))
Java 和 Kotlin
Client.newRpcStub 会创建 Flow 的子类。Java Flow class 和它的 RPC method 都不能是 final。Kotlin 默认将两者都设为 final,所以要将 Flow class 和它的 RPC method 声明为 open。
TypeScript codec
TypeScript 未设置 inputCodec 或 outputCodec 时会使用 JSON。input 或 output 不是 JSON 时需要提供 codec,包括 string、number、boolean 和 bigint 等 scalar value。
然后像下面的例子一样调用 RPC:
result = await client.invoke_rpc(flow.trigger, flow_id, message)
只读 RPC 与 terminal Flow
从 Flow 外读取已存储的 Attribute 值,不需要只读 RPC。Client 的 GetAttributes 直接读取已提交的值,不经过 Worker。见读取 Attribute。当读取需要 Flow 内的逻辑,例如 validation、authorization、Channel 消息或派生视图时,使用只读 RPC。
没有 Attribute lock 且没有启用 transactional execution 的 RPC 会先读取 Flow。如果 handler 只返回 output,不产生 durable effect,Dex 不会改变 Flow,而是直接返回 output。这次读取可以到达仍在 retention 内的 terminal execution。因此,只读 RPC 成功并不能证明 Flow 仍处于 active 状态。
Attribute lock 和显式 transactional execution 都要求 active execution。非 transactional handler 如果返回 Attribute write、Channel operation、event 或 Step movement,也需要 active execution,因为 Dex 必须在 handler 返回后把这些 effect commit 到 Flow。Terminal Flow 会拒绝这次 commit,即使 Worker handler 可能已经运行。Server 也可以配置为以 transactional 方式执行所有 RPC;该路径同样要求 active execution。
响应需要当前 execution status 时,请使用 Flow lifecycle API。Application snapshot RPC 读取的是 Flow 捕获的 durable application state;它不能根据 RPC 是否成功推断 terminal status。FlowNotActiveOrNotFoundError 表示目标不存在,或所选操作无法使用已结束的 execution。Java 使用 FlowNotActiveOrNotFoundException;Rust 使用 SdkError::FlowNotActiveOrNotFound。仅凭这个错误无法区分 Flow 不存在和已结束。
对于已确认没有锁、事务和持久化副作用,且 Server 未强制 transactional 执行的只读 RPC,仍被保留的 terminal execution 可以读取。在这个读取边界,直接把此错误处理为没有可读取的目标,并保留应用契约中“不存在”或“保留期结束后不可用”的结果。不要仅为区分不存在和已结束而增加 lifecycle 查询、带短超时的 WaitForFlow、重试或历史 Step 输出解码。其他服务错误和 Worker 错误应保持独立。
对于 mutation 和要求 active execution 的 RPC,同一错误可能表示 Flow 已结束或不存在。按该操作的业务契约处理;此错误不能证明请求的操作已成功。
普通只读 RPC 无需先搜索 Run ID。SDK 暴露可选 Run ID 时,省略它即可让 Server 为本次调用解析并固定当前 execution。只有应用必须在 Flow ID 重用场景下精确指定某次 execution 时,才需要提供 Run ID。
选择性加载 RPC 状态
每个 RPC 都会收到普通 Attribute value 和 Channel size metadata。读取 AttributeMap entry 或 pending Channel message 时必须显式加载,避免不使用这些大型 collection 的 RPC 仍要接收它们。
Handler 需要所有当前 instance 时,加载整个 AttributeMap 或 ChannelMap;只需要已知 key 时,加载指定 map instance。需要 pending message 的 ID 或 Value 时,加载对应 Channel。Channel size、ChannelMap key 和 size 无需加载 pending message 即可读取。
所有 SDK 都在 RPC definition 上声明 load。Go 在 GetRPCs 中将 RPCOptions 传给 DefineRPC。Dex Server 会根据 Flow persistence schema 验证 requested definition,并随 RPC snapshot 返回已加载范围。Handler 读取选择性加载的 collection 时,SDK 会检查这个范围。
如果 AttributeMap entry 或 instance 不在该范围内,SDK 会抛出 AttributeMapNotLoadedError。如果 pending message 不在该范围内,SDK 会抛出 ChannelMessagesNotLoadedError。Java 使用 AttributeMapNotLoadedException 和 ChannelMessagesNotLoadedException。对于 Go,dexcli visualize 会报告读取未选择状态的 RPC。见状态加载。
显式加载的 collection 可以为空,这不是错误。空的 pending-message snapshot 会返回空列表。不存在的 AttributeMap entry 会使用 SDK 正常的缺失值语义。Load 会建立一份 input snapshot。它不会消费 message,也不会使 RPC 具备 transactional 语义或阻止并发 operation。
运行时选择 map instance
RPC definition options 用于每次调用都相同的固定行为,包括 timeout、普通 Attribute lock、transactionality、whole-map load、singleton Channel load 和固定 exact-instance load。Go 将这个类型命名为 RPCOptions。
当应用 adapter 根据 request data 选择 AttributeMap 或 ChannelMap instance 时,使用 RPCInvokeOptions。Rust 使用 RpcInvokeOptions。Invocation options 只能增加 exact AttributeMap load、exact ChannelMap load 和 AttributeMap instance lock。Dex 会把它们与注册配置 union、排序并去重,不能替换或清除注册配置。
Load 和 lock 相互独立。只读 lookup 只需要 exact load。Read-modify-write RPC 必须为同一个 AttributeMap instance 同时增加 load 和 lock。SDK 会在网络调用前验证 Flow ownership、definition kind,以及 instance 是否非空且不含斜线。
options=RPCInvokeOptions(
lock_attribute_map_instances=(
flow.customer_profiles_by_email_partition.lock(partition_name),
),
load_attribute_map_instances=(
flow.customer_profiles_by_email_partition.load(partition_name),
),
)
例子: examples/python/dex_examples/patterns/hash-partitioned-attribute-map/controller.py
完整 lookup 和 upsert 示例见 Hash 分区 AttributeMap,动态分页 load 示例见 顺序分块 AttributeMap。
Transactional read 与 write
Transactional RPC 会把 RPC 的 read 和 write 作为一个 atomic operation 执行。
持有 Attribute lock 的 RPC 已经隐式具备 transactional 语义。也可以设置 is_transactional=true,显式启用 transactional RPC。这对 Channel message deletion 尤其有用,因为 Dex 会在 commit 任何 write 前验证 message 是否仍处于 pending 状态。
Caller 只传 pending message ID。RPC 加载 source Channel,从 snapshot 读取原始 Value,再暂存 deletion 和 destination publication。如果 concurrent consumer 已取走该消息,deletion validation 会失败,因此 Dex 不会 commit 任何一项 effect:
@rpc(is_transactional=True, load_channels=(queued_messages,))
def move_queued_message_to_prioritized_messages(
self, context: Context, queued_message: QueuedMessageReference
) -> None:
message_to_prioritize = self.queued_messages.find_pending_message(
context, queued_message.message_id
)
self.queued_messages.delete(context, queued_message.message_id)
if message_to_prioritize is not None:
self.prioritized_messages.publish(context, message_to_prioritize.value)
例子: examples/python/dex_examples/primitives/channel/channel_flow.py
Transactional commit 不会在 handler 运行期间提供 isolation。如果决策依赖整个 loaded Channel、ChannelMap 或 AttributeMap 保持不变,所有协作的 Step 与 RPC writer 都必须使用同一把 Attribute lock。该 lock 提供 isolation,并隐式使 RPC 具备 transactional 语义;它不会加载 collection content。
Non-transactional RPC 遇到 missing deletion 时会把它当作 no-op,仍然 commit 其他成功的 effect。
RPC timeout
RPC timeout 限制一次 Worker handler invocation。
所有 SDK 都在 RPC definition 上声明 timeout。Go 将静态 RPCOptions 传给 DefineRPC。Client invocation 不能覆盖它,但 caller context 可以更早停止等待。应用不设置 timeout 时,Dex Server 使用默认值 60 秒,由 api.defaultRpcTimeout 设置。显式设置的 timeout 没有上限。
@rpc(timeout=timedelta(seconds=30))
Attribute locks
Attribute lock 请参阅 Locking。