Convex Workflows、Cloudflare Workflows 与 Dynamic Workflows:区别、场景与使用方式

This article is extracted from the chat log with AI. Please identify it with caution.

说明:本文由 Codex 根据作者提供的主题、对话素材与产品官方文档辅助生成,属于 AI 生成/整理内容,非作者原创。请读者自行甄别并交叉验证。

如果应用已经建立在 Convex 上,优先选 Convex Workflow;如果流程代码由团队随 Worker 一起部署,优先选 Cloudflare Workflows;只有当“流程实现本身”要按租户、用户或 Agent 在运行时加载时,才需要 Cloudflare Dynamic Workflows

三者都属于代码式的 durable execution(持久化执行),可以把多步骤任务拆开、保存已完成步骤、在故障后继续,并支持等待和延迟。真正的差别不在于能否写 if 或循环,而在于:**代码归谁管理、何时确定、状态与业务数据放在哪里,以及是否需要安全地运行动态代码。**本文按 2026-08-07 的官方资料整理;限额、计划可用性与价格应以文末官方链接为准。

先澄清:“Dynamic”不是“有条件分支”#

Convex Workflow 和普通 Cloudflare Workflows 都可以根据输入或前一步结果使用条件、循环、try/catchPromise.all() 决定下一步。它们要求的是恢复时可重放的确定性流程,而不是静态 DAG。

Cloudflare 的 Dynamic Workflows 是另一个层面的“动态”:Workflow 的实现代码不在部署 Worker 时固定,而是由 Worker Loader 在运行时加载。它仍然使用原来的 Cloudflare Workflows 引擎与 step.do()step.sleep()step.waitForEvent()@cloudflare/dynamic-workflows 只补上“实例恢复时,如何回到正确动态代码”的路由层。Convex Workflow README Cloudflare Dynamic Workflows 指南

换言之,订单流程按订单金额走不同审批路径,使用普通 Cloudflare Workflows 即可;若每个租户都能上传自己的审批代码,或 Agent 在运行时生成一段新的流程实现,才需要 Dynamic Workflows。

两层“动态”的本质差别#

层次Convex WorkflowCloudflare WorkflowsCloudflare Dynamic Workflows
数据/控制流动态:根据输入、配置、前一步结果选择分支、循环次数或并行任务支持;handler 以确定性方式重放支持;run() 可使用 if、循环、try/catch 与 Promise支持,和普通 Workflows 相同
代码动态:针对不同租户/任务在运行时换一份可执行流程实现不支持;workflow 是随 Convex 应用部署的代码不支持;class_name 指向随 Worker 部署的实现支持;Loader 运行时加载 Dynamic Worker,并把实例恢复路由回该实现

所以 Convex Workflow 本质上更接近普通 Cloudflare Workflows:**两者都是由开发团队发布并维护流程代码,实例只携带不同的数据。**Convex 允许流程本身有非常动态的控制流,但已在运行的实例仍须遵守同一份确定性步骤历史;变更步骤的增删或顺序需要通过部署和迁移策略处理,不能把它理解为“用户能随时塞入一段新流程代码”。Cloudflare Dynamic Workflows 才是把“流程实现”也变成运行时输入的方案。Convex Workflow 限制 Cloudflare Workers API Cloudflare Dynamic Workflows

一张表看清边界#

维度Convex WorkflowCloudflare WorkflowsCloudflare Dynamic Workflows
本质安装进 Convex 应用的 Component,底层使用 WorkpoolWorkers 平台原生的 durable execution 产品Dynamic Workers 与 Workflows 的适配库,不是第三个执行引擎
流程代码何时确定随 Convex 部署确定WorkflowEntrypointclass_name 在 Wrangler 部署时确定请求/任务运行时,按租户、版本或任务加载
主要步骤 APIrunQueryrunMutationrunActionsleepawaitEventstep.dosleepsleepUntilwaitForEvent与普通 Workflows 相同
持久化与恢复将步骤历史写入 Convex;handler 确定性重放到下一个未完成步骤引擎保存成功步骤结果并从持久化边界恢复同普通 Workflows,另依据 metadata 重新加载对应动态 Worker
业务数据与运行时与 Convex 函数、数据库、事务、响应式查询紧耦合与 Workers、R2、D1、KV、Queues、Workers AI 等 binding 集成还要设计动态代码的 bundle、版本、能力授权与审计
状态观察在 Convex query 中读取状态即可获得响应式更新Dashboard、Workers API、CLI、REST API、日志同普通 Workflows,外加租户/代码版本维度的观测
动态业务分支支持,但 workflow handler 必须可确定性重放支持,但步骤名、步骤外副作用和非确定性逻辑要遵守恢复规则支持;并且流程实现代码也可动态变化
最适合已采用 Convex 的产品后端与 Agent 流固定代码的边缘后端、数据管道、Webhook、审批多租户自动化平台、用户/AI 提供代码的可编程平台

Convex Workflow 是 @convex-dev/workflow 组件,而非一个独立的静态工作流服务;其官方实现会记录每个步骤,并在后续执行时重放已记录历史。Cloudflare Workflows 则是绑定到部署 Worker 的平台能力;Dynamic Workflows 通过 @cloudflare/dynamic-workflows 让这套能力可用于运行时加载的 Worker 代码。Convex 组件页 Cloudflare Workflows 概览

核心模型相同,但宿主平台不同#

若只看 durable execution,Convex Workflow 与普通 Cloudflare Workflows 是同一类抽象:都把业务代码划分为可持久化的步骤,故障后跳过已经完成的步骤并继续执行。因此,订单履约、延迟通知、审批和数据管道等流程的编程心智模型非常接近;但它们并不是只替换 API 名称就能迁移的同一项服务。

选择层Convex WorkflowCloudflare Workflows
运行与业务数据Component 运行在 Convex 应用内;步骤调用 Convex query、mutation、action,流程状态可用响应式 query 读取原生运行在 Workers 平台;流程通过 Worker binding 与 R2、D1、KV、Queues 等能力集成
恢复边界handler 根据已记录的步骤历史确定性重放Workflows 引擎持久化步骤结果,并以 WorkflowEntrypoint.run() 的步骤边界恢复
重试与演进Convex action 步骤默认不重试,需按副作用和幂等性显式选择;活动实例受确定性步骤历史约束step.do() 有平台默认重试策略且可逐步配置;也必须遵守确定性与步骤命名规则
代码所有权开发团队部署和维护 Convex 应用代码开发团队部署和维护 Worker 代码

所以,已经使用 Convex 的应用应把它当作应用内的 durable orchestration;已经使用 Workers 的系统则应把 Cloudflare Workflows 当作Worker 生态内的 durable orchestration。只有当流程的实现代码也必须成为运行时输入时,才进入 Cloudflare Dynamic Workflows 的选型范围。Convex Workflow README Cloudflare Workers API Cloudflare Dynamic Workflows

三种运行模型#

flowchart LR
  subgraph C["Convex Workflow"]
    C1["mutation/action: start()"] --> C2["WorkflowManager handler"]
    C2 --> C3["Convex query / mutation / action steps"]
    C3 --> C4["Convex 中的步骤历史与状态"]
  end

  subgraph W["Cloudflare Workflows"]
    W1["HTTP / Queue / Cron: binding.create()"] --> W2["WorkflowEntrypoint.run()"]
    W2 --> W3["step.do / sleep / waitForEvent"]
    W3 --> W4["Workflows 引擎中的实例与步骤状态"]
  end

  subgraph D["Cloudflare Dynamic Workflows"]
    D1["Worker Loader"] --> D2["按租户/版本加载 Dynamic Worker"]
    D2 --> D3["wrapped WORKFLOWS.create()"]
    D3 --> W4
    W4 --> D4["DynamicWorkflow bridge"]
    D4 --> D1
  end

1. Convex:确定性重放的应用内编排#

Convex 的 workflow handler 像一个普通 TypeScript 函数,但它在步骤运行期间并不持续占用执行资源:组件记录步骤结果,随后重新执行 handler,并确定性地重放历史,直到抵达下一个未完成步骤。步骤可以是 Convex query、mutation、action 或另一个 workflow;可以串行,也可以用 Promise.all() 并行。状态可通过普通 Convex query 查询,因此前端能利用 Convex 的响应式订阅实时显示进度。运行原理与可观察性 状态查询

这一模型的好处是业务数据、事务、鉴权和进度 UI 都留在一个 Convex 应用内。代价是 handler 必须保持确定性:不能直接在 handler 中使用 fetch、环境变量或 crypto 等能力,网络或其他副作用应放在 step.runAction() 里;运行中的流程若新增、删除或重排步骤,会触发 determinism violation。使用限制

2. Cloudflare:固定部署代码的 Worker 原生编排#

Cloudflare Workflows 通过 Wrangler 中的 workflows[].class_name 把一个 WorkflowEntrypoint 子类注册为流程定义。每次 env.MY_WORKFLOW.create({ params }) 都创建一个独立实例;step.do() 的成功结果会被保存,在失败或 Worker 重启后不必重新执行已成功步骤。sleepsleepUntilwaitForEvent 适合把定时、Webhook 与人工审批纳入同一流程。Workers API 事件与参数

它的优势是自然接入 Workers 的边缘入口和平台 binding,例如由 Queue consumer 创建实例、从 R2 取文件、用 D1 读写业务数据,或调用 Workers AI。业务流程代码仍随应用部署,并非由最终用户在运行时提供。

3. Cloudflare Dynamic Workflows:把“恢复路由”加到动态代码上#

普通 Cloudflare Workflow 的类名在部署时固定。Dynamic Workflows 引入三段式结构:已部署的 Worker Loader 认证请求并选择代码;按租户或版本按需载入的 Dynamic Worker 定义实际步骤;库生成的 DynamicWorkflow bridge 在实例恢复时读取 metadata,再让 Loader 取回同一份动态代码。动态 Worker 看见的仍是普通 Workflow binding,所以它的步骤 API、重试、睡眠、事件、状态和暂停语义与普通 Workflows 保持一致。官方架构与使用方式

这解决的不是“怎样写更复杂的业务分支”,而是“一个固定的部署入口如何长期、可靠地运行许多不同的流程实现”。例如 SaaS 让每个客户定义 webhook 链和审批规则,或 AI Agent 为每个任务生成不同的长期计划。

使用方式:从最小骨架理解 API#

以下示例用于对照 API 形状,省略项目内已有的业务函数、鉴权和部署配置,未连接真实服务执行。上线前应按各自官方快速开始指南在目标项目中完成验证。

Convex Workflow#

先安装并把 Component 注册到 Convex 应用:

npm install @convex-dev/workflow
// convex/convex.config.ts
import workflow from "@convex-dev/workflow/convex.config.js";
import { defineApp } from "convex/server";

const app = defineApp();
app.use(workflow);
export default app;

定义流程时,handler 通过 step 调用项目内的 Convex 函数。下面的 internal.orders.* 是同一 Convex 项目中已定义的内部函数;runAction 显式开启重试,外部调用要自己保证幂等。

// convex/ordersWorkflow.ts
import { WorkflowManager, start } from "@convex-dev/workflow";
import { v } from "convex/values";
import { mutation } from "./_generated/server";
import { components, internal } from "./_generated/api";

const workflow = new WorkflowManager(components.workflow);

export const fulfillOrder = workflow
  .define({ args: { orderId: v.string() } })
  .handler(async (step, { orderId }): Promise<void> => {
    const order = await step.runQuery(internal.orders.get, { orderId });

    await step.runAction(
      internal.shipping.createLabel,
      { orderId, address: order.address },
      { retry: true },
    );

    await step.awaitEvent({ name: "payment-confirmed" });
    await step.runMutation(internal.orders.markFulfilled, { orderId });
  });

export const startFulfillment = mutation({
  args: { orderId: v.string() },
  handler: (ctx, { orderId }) =>
    start(ctx, internal.ordersWorkflow.fulfillOrder, { orderId }),
});

start() 异步返回 WorkflowId,调用者不直接等待 handler 的结果;可用 getStatus() 查询,或在 Convex query 中读取以获得响应式更新。step.sleep()step.awaitEvent()sendEvent() 分别处理延迟和外部信号,另有 cancel()restart()cleanup() 等管理 API。已完成流程不会自动清理历史,需在 onComplete 或定时任务中处理。安装、启动与完成处理 等待事件 清理

要特别注意默认重试:Convex 的 query、mutation 与 workflow handler 具有 Convex 自身的系统错误重试和事务保证,但 step.runAction() 默认重试。为 action 开启重试后,仍要用外部服务的 idempotency key 或业务去重防止重复副作用。重试行为 Action 的错误处理

深入阅读:Convex Workflow:接口、事件与恢复 API 实战

Cloudflare Workflows#

普通 Workflows 需要在 wrangler.jsonc 中把固定的类名绑定到 Workflow:

{
  "name": "orders-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-08-07",
  "workflows": [
    {
      "name": "order-workflow",
      "binding": "ORDER_WORKFLOW",
      "class_name": "OrderWorkflow"
    }
  ]
}

流程本体继承 WorkflowEntrypoint。这段最小示例展示了持久化步骤、休眠和等待外部事件:

import {
  WorkflowEntrypoint,
  type WorkflowEvent,
  type WorkflowStep,
} from "cloudflare:workers";

type Params = { orderId: string };

export class OrderWorkflow extends WorkflowEntrypoint<{}, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    const order = await step.do("capture order", async () => ({
      orderId: event.payload.orderId,
    }));

    await step.sleep("payment grace period", "1 hour");
    const payment = await step.waitForEvent<{ paid: boolean }>(
      "wait for payment",
      { type: "payment-confirmed", timeout: "7 days" },
    );

    return step.do("finish order", async () => ({
      ...order,
      status: payment.payload.paid ? "paid" : "unpaid",
    }));
  }
}

HTTP handler、Queue consumer、Cron、CLI 和 REST API 都可以创建实例,例如 await env.ORDER_WORKFLOW.create({ params: { orderId } })。Cloudflare 对未自定义配置的 step.do() 默认使用 limit: 5、10 秒延迟、指数退避与 10 分钟每次尝试超时;可按步骤修改,也可用 NonRetryableError 表达不会因重试而改善的永久错误。触发 Workflows 睡眠与重试

Cloudflare Dynamic Workflows#

Dynamic Workflows 同时配置 worker_loaders 和一个指向 bridge 的普通 Workflow binding,并安装库:

npm install @cloudflare/dynamic-workflows
{
  "worker_loaders": [{ "binding": "LOADER" }],
  "workflows": [
    {
      "name": "dynamic-workflow",
      "binding": "WORKFLOWS",
      "class_name": "DynamicWorkflow"
    }
  ]
}

宿主侧的关键是给动态代码包装 binding,并在恢复时依据持久化 metadata 重新载入正确的 Worker。下段是 bridge 的核心形状;实际项目还需要实现认证、读取不可变代码 bundle 和最小权限 binding:

import {
  createDynamicWorkflowEntrypoint,
  DynamicWorkflowBinding,
  wrapWorkflowBinding,
  type WorkflowRunner,
} from "@cloudflare/dynamic-workflows";

interface Env {
  LOADER: WorkerLoader;
}

declare function loadBundle(
  tenantId: string,
  codeVersion: string,
): Promise<string>;

export { DynamicWorkflowBinding };

export const DynamicWorkflow = createDynamicWorkflowEntrypoint<Env>(
  async ({ env, metadata }) => {
    const { tenantId, codeVersion } = metadata as {
      tenantId: string;
      codeVersion: string;
    };

    const worker = env.LOADER.get(`${tenantId}:${codeVersion}`, async () => ({
      compatibilityDate: "2026-08-07",
      mainModule: "index.js",
      modules: { "index.js": await loadBundle(tenantId, codeVersion) },
      env: {
        WORKFLOWS: wrapWorkflowBinding({ tenantId, codeVersion }),
      },
      globalOutbound: null,
    }));

    return worker.getEntrypoint("TenantWorkflow") as unknown as WorkflowRunner;
  },
);

动态代码内定义的 TenantWorkflow extends WorkflowEntrypoint 与普通 Workflow 写法相同,也照常调用 env.WORKFLOWS.create()。关键区别是 bridge 会用 tenantIdcodeVersion 找回创建实例时的代码版本。DynamicWorkflowBinding 必须 re-export;而 wrapWorkflowBinding() 的 metadata 会被持久化,不能放 API key、token 或其他 secret。完整设置

场景选型#

场景建议原因
Convex 应用的注册、延迟通知、订单后处理、Agent 编排Convex Workflow复用 Convex 数据模型、内部函数、事务与实时 UI,系统边界最少。
R2 上传后处理、Queue 消费后的多步管道、Webhook 等待、边缘用户生命周期任务Cloudflare Workflows固定代码直接接入 Workers 和 Cloudflare binding,部署与运营模型简单。
每个 SaaS 租户都有不同的审批、计费重试、Webhook 或数据转换代码Cloudflare Dynamic Workflows不必为每个租户部署一个 Workflow class,且可在 sleep/故障后找回对应实现。
Agent 仅根据输出选择下一工具或下一分支Convex Workflow 或普通 Cloudflare Workflows这是数据驱动的控制流,不是运行时装载新代码。
Agent / 用户要生成并执行任意新代码,还要跨天等待与恢复Cloudflare Dynamic Workflows动态代码是它的一等场景;但必须实施沙箱、版本和能力治理。
高吞吐消息削峰而不需要多步骤状态Queue / Workpool 等队列模型先用消息队列解决投递和消费;只有需要跨步骤状态、等待或补偿时再引入 workflow。

一个实用的决策顺序是:

  1. 已经把业务事实和客户端状态放在 Convex 吗?是则先评估 Convex Workflow。
  2. 流程实现是否由团队部署且能在部署时确定?是则用普通 Cloudflare Workflows,不要因流程有循环而引入 Dynamic Workflows。
  3. 是否要让外部租户或 Agent 在运行时提供可执行的流程实现?只有答案为“是”,才承担 Dynamic Workers 的代码加载、权限、审计和版本恢复复杂度。

可靠性与运维:三个共同的坑#

外部副作用仍须幂等#

持久化步骤避免了“成功步骤因为后续失败而整体重跑”,但不能把跨网络的副作用自动变成分布式 exactly-once。例如第三方已经扣款、但响应丢失时,重试仍可能再次发起调用。无论使用哪一种,都应为扣款、发信、创建资源和 webhook 写入设计幂等键、去重记录或补偿步骤。Cloudflare 也明确要求不要把副作用放在步骤外,并要求步骤名称稳定;Convex 则应谨慎为 action 开启重试。Cloudflare Workflow 规则 Convex 重试规则

在“恢复”前先设计版本策略#

Convex 运行中的 workflow 对步骤增加、删除和重排敏感;此类变更会造成确定性违规。Cloudflare 普通 Workflows 同样要求稳定的步骤名和可重放控制流。Dynamic Workflows 还多了一层:metadata 应携带不可变的代码版本,Loader 必须能在数日后的恢复时取回同一版本 bundle,而不是租户当前的最新代码。这是由官方的 metadata 恢复机制推导出的工程要求。Convex 限制 Cloudflare Dynamic Workflows

不要把工作流状态当大对象仓库#

Convex 规定一次 workflow 执行中步骤参数与返回值总量最多 1 MB,并保留 8 MiB journal 上限;Cloudflare 对非流式 step 返回值和事件 payload 也设有 1 MiB 上限。两者都应把大文件、长文本和批量数据写入各自的数据库或对象存储,只在步骤间传 ID、对象 key、页码或 checksum。Convex 限制 Cloudflare 限额

结论#

这不是“哪一个更强”的三选一,而是三层不同的问题:

  • Convex Workflow:在 Convex 应用边界内,把可靠的长期编排与数据库、函数、事务和响应式 UI 放在一起。
  • Cloudflare Workflows:在 Workers 平台内运行由团队部署的、固定实现的耐久多步骤流程。
  • Cloudflare Dynamic Workflows:在普通 Cloudflare Workflows 之上,为运行时加载的多租户或 Agent 代码增加可恢复路由;能力最灵活,但安全和版本治理成本也最高。

因此,若只是流程“会根据数据动态分支”,请选前两者中与你现有平台相符的那个;只有流程代码需要动态加载时,才选第三者。

继续深入#

参考资料#

本文共 6133 字,创建于 Aug 7, 2026

相关标签: Cloud, Serverless, DevOps, TypeScript, ByAI