说明:本文由 Codex 根据作者提供的主题、对话素材与 Cloudflare 官方文档辅助生成,属于 AI 生成/整理内容,非作者原创。请读者自行甄别并交叉验证。
Dynamic Workers 解决“现在加载哪段代码”;Workflows 解决“这次流程在 sleep、重试或等待事件后如何继续”。Dynamic Workflows 解决二者结合后最棘手的问题:一个数天前创建的实例恢复时,怎样找回当时那一版租户代码,而不是不小心执行当前最新版本。
这是多租户自动化平台的进阶能力,不是普通固定业务流程的默认选择。先理解 Dynamic Workers:Worker Loader、受控 Binding 与安全执行 和 Workflows:实例、步骤、事件与恢复 API 实战 再使用本页。
1. 恢复路径:metadata 是路由信息,不是秘密仓库#
flowchart LR A["请求:tenantId + codeVersion"] --> H["可信宿主 Worker"] H --> L["Worker Loader:加载不可变代码版本"] L --> D["Dynamic Worker:TenantWorkflow"] D --> W["wrapWorkflowBinding(metadata)"] W --> I["Workflows instance"] I --> S["sleep / wait / retry / 进程回收"] S --> E["DynamicWorkflow entrypoint"] E --> H H --> L L --> D
metadata 必须至少包含租户 ID、代码版本或内容 hash 等可公开的、可序列化的路由信息。它会随实例状态持久化,不能放 API key、数据库密码、OAuth token 或任何 secret。恢复流程只能依据 metadata 加载正确的 immutable bundle。
2. 最小配置:一个 Loader,一个普通 Workflow binding#
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "tenant-automation",
"main": "src/index.ts",
"compatibility_date": "2026-08-07",
"worker_loaders": [
{ "binding": "LOADER" }
],
"workflows": [
{
"name": "dynamic-workflow",
"binding": "WORKFLOWS",
"class_name": "DynamicWorkflow"
}
]
}这里的 WORKFLOWS 是真正由 Workflows Engine 管理的 binding;Dynamic Worker 不能直接把它原样跨 RPC 边界使用,而要由 wrapWorkflowBinding() 包装。
3. 宿主:加载版本并提供恢复入口#
import {
createDynamicWorkflowEntrypoint,
DynamicWorkflowBinding,
wrapWorkflowBinding,
type WorkflowRunner,
} from "@cloudflare/dynamic-workflows";
export { DynamicWorkflowBinding };
type RouteMetadata = {
tenantId: string;
codeVersion: string;
};
function workerId(route: RouteMetadata) {
return "tenant:" + route.tenantId + ":workflow:" + route.codeVersion;
}
function loadTenantWorkflow(env, route: RouteMetadata) {
return env.LOADER.get(workerId(route), async () => ({
compatibilityDate: "2026-08-07",
mainModule: "index.js",
modules: {
"index.js": await readImmutableBundle(
route.tenantId,
route.codeVersion,
),
},
globalOutbound: null,
env: {
WORKFLOWS: wrapWorkflowBinding(route),
CRM: makeTenantScopedCrmCapability(route.tenantId),
},
limits: { cpuMs: 50, subRequests: 20 },
}));
}
export const DynamicWorkflow = createDynamicWorkflowEntrypoint(
async ({ env, metadata }) => {
const route = metadata as RouteMetadata;
return loadTenantWorkflow(env, route).getEntrypoint("TenantWorkflow")
as unknown as WorkflowRunner;
},
);这段宿主代码有三项职责:
- 强制版本不可变。
readImmutableBundle()必须按 tenant + version/hash 返回同一份内容。代码、依赖、权限或 compatibility 配置改变时,版本号随之改变。 - 重新建立 capability。 恢复时重新创建给该租户的 CRM、存储或出站网关 binding;不把原始资源或 secret 塞进动态代码。
- 提供 Engine 可调用的入口。
createDynamicWorkflowEntrypoint()在每次恢复时读 metadata,重新加载正确 entrypoint。
4. 租户代码:看起来像普通 Workflow#
租户 bundle 内的 Workflow 写法接近普通 Workflows,只是 env.WORKFLOWS 已被包装,创建实例时会自动附带 metadata:
import { WorkflowEntrypoint } from "cloudflare:workers";
export class TenantWorkflow extends WorkflowEntrypoint {
async run(event, step) {
const updated = await step.do("update tenant CRM", async () => {
return this.env.CRM.upsert(event.payload);
});
await step.sleep("wait for human review", "1 day");
const review = await step.waitForEvent(
"wait for approval",
{ type: "approval", timeout: "7 days" },
);
return { updated, review };
}
}
export default {
async fetch(request, env) {
const params = await request.json();
const instance = await env.WORKFLOWS.create({ params });
// 这里跨 Dynamic Worker 的 RPC 边界,id 是 RpcPromise。
return Response.json({ id: await instance.id });
},
};Dynamic Workflow 的 status()、等待、重试、暂停、恢复和终止语义来自普通 Workflows;库添加的是“实例状态携带路由 metadata,恢复时自动回到该版本动态代码”。
5. 接口地图与最常见故障#
| 组件 | 接口 / 约定 | 必须保证的事情 |
|---|---|---|
| 宿主入口 | env.LOADER.get(id, callback) | ID 与 callback 必须对应同一份不可变代码和配置。 |
| 动态代码 | wrapWorkflowBinding(metadata) | 每个 create()/createBatch() 都携带可恢复路由信息。 |
| Engine 入口 | createDynamicWorkflowEntrypoint(loadRunner) | 运行/恢复时由 metadata 找回对应 runner。 |
| 必须 export | DynamicWorkflowBinding | runtime 依靠它创建给动态代码的包装 binding。 |
| instance ID | await instance.id | Dynamic Worker 侧通过 RPC 获得 ID,不要漏掉 await。 |
| metadata | tenant ID、version/hash、非敏感路由键 | JSON 可序列化;绝不存 secret。 |
四个高频问题:
- 只保存 tenantId,不保存版本。 旧实例恢复时执行了“最新代码”,可能改变审批逻辑、权限或副作用。
- 忘记 re-export
DynamicWorkflowBinding。 动态代码没有正确的 Workflow wrapper,创建实例会在运行期出错。 - 把原始 Workflow binding 直接传给动态代码。 必须用 wrapper 让 metadata 跟随实例;否则恢复路径缺失。
- 把 metadata 当配置仓库。 metadata 只存路由,敏感值应保留在宿主 Worker 的 secrets 或受控 Service Binding 中。
6. 什么时候值得承担这层复杂度#
Dynamic Workflows 很适合客户自定义 onboarding、审批链、webhook 自动化,或 AI Agent 为不同租户生成长期任务计划。它不适合流程定义固定、只有一两个同步步骤、没有用户代码的常规后端业务;这类场景直接选择 Workflows 更容易审计、测试和升级。
上线前至少测试:sleep 后恢复、事件提前到达、同一租户新旧版本并存、bundle 缺失、权限变更、外部副作用重试、metadata 错误和强制终止时的补偿路径。