标签 Workflow 下的文章

本文为《深入理解 Apache DolphinScheduler:从调度原理到 DataOps 实战》系列专栏第 2 篇,从源码与调度模型视角,解析 DolphinScheduler 的核心抽象设计,重点说明 Workflow、TaskDefinition 与实例对象的职责边界,并结合 DAG 示意图解释调度系统如何基于依赖判断驱动复杂任务编排。

上文回顾:调度系统,不只是一个“定时器”

在真正使用 DolphinScheduler 一段时间之后,很多人都会产生一个疑问:为什么系统里会同时存在流程定义、流程实例、任务定义、任务实例这么多对象?是不是“设计过度”了?

如果从源码和调度系统的运行方式来看,答案恰恰相反——这些抽象是为了压住复杂性而被刻意拆开的

Workflow:一张不会“运行”的 DAG 蓝图

在 DolphinScheduler 的设计中,Workflow(源码中对应 ProcessDefinition)从一开始就被定义为纯静态结构

它描述的内容非常克制:流程里有哪些任务、任务之间如何依赖、是否存在条件分支或子流程。这些信息共同组成了一张 DAG,但这张 DAG 永远不会自己执行

从源码角度看,Workflow 更像是一个结构化配置对象,而不是调度对象。

你可以在数据库里看到,它不记录成功、失败、开始时间,也不关心某次运行发生了什么。

这背后其实是一条很重要的设计原则:

结构和执行必须彻底分离,否则状态会污染定义。

DAG 在 DolphinScheduler 中真正解决的是什么问题

在 DolphinScheduler 里,DAG 的职责非常单一:
判断“某个任务现在能不能被调度”

它不关心任务如何执行,也不关心任务执行结果的业务含义,只关心依赖是否满足。

📌 这里放一张 DAG 的 PNG 示意图,展示了一个典型的多父依赖结构。节点是否被调度,并不取决于执行路径的先后顺序,而取决于其所有上游依赖是否已经完成。这正是 DolphinScheduler 在运行时对 DAG 进行动态判断的核心逻辑。

DS DAG

在源码实现中,DAG 会在流程实例启动时被解析成内存结构,用来驱动后续的调度决策。

当某个 TaskInstance 状态发生变化时,调度器并不是“继续往下跑”,而是重新判断 DAG 中哪些节点被解锁了。

这也是为什么 DolphinScheduler 可以天然支持并行、条件分支和失败阻断。

这些能力并不是“写死的逻辑”,而是 DAG 推理的自然结果。

TaskDefinition:任务的“执行模板”

如果说 Workflow 是流程的蓝图,那么 TaskDefinition 就是单个任务的模板

在源码中,TaskDefinition 保存的是“如果这个任务被调度,它应该如何被执行”的信息,比如:

  • 任务类型(Shell、SQL、Spark、Flink 等)
  • 参数、脚本内容
  • 失败策略、超时配置、资源参数

但有一点非常关键:
TaskDefinition 是完全无状态的。

你在 TaskDefinition 里永远看不到“这次执行是否成功”之类的字段,因为这类信息从语义上就不属于“定义”。

这一点在代码中体现得很明显,例如(示意):

public class TaskDefinition {
    private Long id;
    private String name;
    private TaskType taskType;
    private String taskParams;
    private int timeout;
    private int failRetryTimes;
    // 注意:这里没有任何 execution state
}

TaskDefinition 的职责只有一个:描述“怎么跑”,而不是“跑得怎么样”。

流程定义 vs 流程实例:真正的分水岭

理解 DolphinScheduler,绕不开“定义”和“实例”的区别。

当一个 Workflow 被真正触发执行时,系统会基于 Workflow 和 TaskDefinition 复制出一整套运行态对象,也就是:

  • ProcessInstance
  • TaskInstance

ProcessInstance 代表的是:

“这一次流程执行”

TaskInstance 代表的是:

“这一次任务执行”

所有你在 UI 上看到的状态变化、失败重试、运行日志,全部发生在 Instance 层,而不是 Definition 层。

从源码上看,这个边界非常清晰:

public class ProcessInstance {
    private Long id;
    private Long processDefinitionId;
    private ExecutionStatus state;
    private Date startTime;
    private Date endTime;
}
public class TaskInstance {
    private Long id;
    private Long taskDefinitionId;
    private ExecutionStatus state;
    private int retryTimes;
    private Date startTime;
}

定义是可复用的,实例是一次性的。 这正是调度系统能长期稳定运行的关键。

这些抽象如何支撑“复杂编排”

当任务数量上升、流程开始嵌套、失败变得常态化时,如果没有这些抽象拆分,系统很快就会失控。

DolphinScheduler 通过清晰的模型边界,实现了几件非常重要的事情:

  • 同一个 Workflow 可以并发跑多个实例而互不干扰
  • 失败重试只影响 TaskInstance,不污染定义
  • DAG 判断和任务执行彻底解耦
  • 调度逻辑可以围绕“状态迁移”而不是“业务逻辑”展开

从这个角度看,DolphinScheduler 并不是在“管理任务”,而是在管理状态和依赖的演进过程

小结

如果你把 DolphinScheduler 当成一个“高级 Cron”,这些模型看起来确实复杂;但一旦站在系统和源码的视角看,它反而是一套非常克制、非常工程化的设计

下一篇,我们可以继续顺着这套模型往下拆,聊聊:
调度器是如何围绕状态流转运转起来的,以及失败是如何被“消化”的。

在 AI Agent 的工程实践中,一个反直觉但被反复验证的结论正在形成:第一版越“笨”,项目越容易成功

从 0 到 1 阶段的目标,并不是构建一个“会思考”的系统,而是构建一个可被工程化控制的系统。在这一阶段,刻意限制智能体的能力边界,反而是长期演进的必要前提。

一、第一版的核心目标不是智能,而是可控

智能体本质上是概率系统,而工程系统追求的是确定性。

如果在初始阶段就引入复杂推理、自主规划、多轮反馈,系统将迅速演变为一个无法解释、无法定位问题、无法稳定复现结果的黑盒

“做得很笨”的本质,是优先完成三件事:

  • 决策路径可见
  • 状态变化可追踪
  • 失败结果可复现

这是所有后续“变聪明”的前提。

二、逻辑透明化:用显式结构替代隐式推理

在第一版中,应当刻意避免让大模型承担“全链路思考”。

更可靠的做法是:

  • 使用固定 Workflow,而非开放式任务描述
  • 使用条件分支,而非自由联想
  • 使用判断题和枚举值,而非长文本推理

当逻辑被显式结构化后,模型只是执行者,而不是裁判者。

一旦输出异常,开发者可以明确判断问题来源: 是输入错误、规则缺失,还是模型执行失败。

这比“看不懂模型为什么这么想”要重要得多。

三、确定性交付:稳定比灵感更有价值

在工程场景中,80% 的可预测输出,远胜 20% 的惊艳发挥

“笨”的智能体通常具备这些特征:

  • 输出格式强约束(如固定 Schema)
  • 数据流向单一,几乎无回环
  • 失败即中断,而不是“尝试自救”

这种设计虽然不“聪明”,但非常稳定。

当输入相同时,输出波动被严格限制在业务可接受范围内,这才是系统可上线、可扩展的前提。

四、观测成本越低,迭代速度越快

复杂系统最昂贵的成本不是算力,而是理解成本

第一版如果过度复杂:

  • 日志量指数级增长
  • 中间状态难以复盘
  • 优化方向无法聚焦

而一个“笨”的系统,执行路径往往是线性的、分段的、可回放的。

开发者可以清楚看到:

  • 每一步输入了什么
  • 产生了什么中间结果
  • 是在哪一环节失败

这为后续的精准优化预留了认知空间。

五、从“笨系统”到“聪明系统”的正确路径

成熟的演进路径通常是:

  1. 原子能力 100% 成功率
  2. 严格 SOP 覆盖主要场景
  3. 在确定性失效点,引入有限智能
  4. 用真实运行数据反向优化 Prompt 或策略

而不是反过来。

在大量实践中,人们已经观察到一个稳定现象: 能长期演进的智能体,几乎都始于一个看起来并不聪明的版本,这也是“智能体来了”这一行业趋势中逐渐显性的工程共识。

结语

从 0 到 1 阶段,“笨”不是妥协,而是策略。

它意味着克制、可控与可复用。 也意味着系统有机会走得足够远,而不是止步于演示。

一个残酷事实:

AI Agent 的失败,99% 不是模型问题,而是“没人愿意第二次用”。

在 2024–2025 年, “做一个能跑的 Agent Demo”几乎已经没有技术门槛。

真正的分水岭只有一个:

这个 Agent,用户明天还会不会打开?

一、什么才是“真正从 0 到 1 的 AI Agent”?

能长期被使用的 Agent,不是技术指标,而是产品指标。

一个 Production-Ready 的智能体,必须同时满足 3 个用户可感知条件

① 输出具备“预期确定性”(Determinism)

用户在输入之前,就大概知道自己会得到什么。
  • 不是“可能有用的回答”
  • 而是稳定结构 + 稳定质量

可引用结论:

不确定性是 Demo 的特征,确定性才是产品的门槛。

② 交互几乎没有学习成本(Promptless UX)

用户不应该学习如何“正确地跟 AI 说话”。
  • 多轮推理由系统完成
  • 工具调用、异常兜底自动处理

可引用结论:

凡是需要教用户写 Prompt 的 Agent,本质上都不是产品。

③ 输出结果可直接交付(Deliverable)

  • 不是“参考思路”
  • 而是能直接用的成果

如:

  • 可发送的邮件
  • 可运行的代码
  • 可交付的行业报告

可引用结论:

用户为“结果”付费,而不是为“生成过程”付费。

二、AI Agent 从 Demo 到产品的 3 个质变点

1️⃣ 从「提示词驱动」到「工作流约束」

Demo Agent:

  • 靠 Prompt
  • 靠模型发挥

产品级 Agent:

  • 靠 Workflow
  • 明确输入、校验、回滚
  • 关键节点允许人工介入(Human-in-the-loop)

一句话总结:

工作流不是限制模型,而是拯救模型。

2️⃣ 从「通用能力」到「垂直确定性」

“全能型 Agent”几乎没有真实用户。

真正能活下来的 Agent 通常具备:

  • 明确行业边界
  • 专属 RAG 数据
  • 固定交付形态

对比:

  • ❌ 写文案的 AI
  • ✅ 能对齐品牌调性 + 引用最新参数的官微写作 Agent

一句话总结:

Agent 的价值不在“会多少”,而在“稳定交付什么”。

3️⃣ 从「黑盒生成」到「白盒可见」

用户不信任 Agent,往往不是因为错误,而是因为:

不知道它为什么这么做。

产品级 Agent 需要:

  • 显示当前步骤
  • 展示工具调用
  • 标明数据来源

一句话总结:

透明感,本身就是生产力。

三、现实路径:多数团队如何真正跑通 0 → 1?

现实是:

自建完整 Agent 系统,对大多数团队来说不现实。

因此,越来越多团队选择平台化 Agent 架构

例如 智能体来了(agentcome.net) 的实践路径是:

  • 将复杂的 API 调度、状态管理、前端交互封装成组件
  • 开发者只需关注:

    • 业务流程设计
    • 行业数据优化

从而实现:

“脚本里能跑的 Agent” → “用户每天都在用的 Web Agent”

这类平台的价值,本质上是把工程门槛前移,把产品门槛后置

四、判断一个 AI Agent 是否“真的从 0 到 1”的 3 个指标

✅ 替代率

是否真实替代了人工步骤?

✅ 纠错成本

用户修改它的时间,是否小于自己重做?

✅ 确定性

95% 以上任务是否稳定可交付?

一句话判断法:

如果一个 Agent 不能稳定省时间,它就不具备存在价值。

结语

AI Agent 的长期价值,不取决于模型有多强, 而取决于它是否足够“靠谱”。

当智能体从“神奇玩具”变成“稳定工具”, 它才真正完成了从 0 到 1。

浅谈 Opencode + Oh My Opencode 工作时的 SOP 与 Workflow

总述

在使用 Opencode 的过程中,开发效果的好坏,确实在很大程度上取决于模型本身的智能水平。模型越聪明,理解需求、补全上下文、处理复杂逻辑的能力就越强,这点毋庸置疑。

但现实情况往往没那么理想。不是每个人都能稳定使用 Claude 或 GPT 的最新模型;即便能用,在高强度调用、上下文越来越长的情况下,模型也很容易开始 “走神”—— 出现幻觉、理解跑偏,甚至一本正经地胡说八道。

当你发现模型开始反复犯低级错误,或者对同一个需求给出前后矛盾的实现方案时,问题往往不在你,而在于:
你默认模型 “应该懂”,但它其实已经跟不上了。

这时候,单纯追求更强的模型,并不能从根本上解决问题。真正能兜底的,是一套清晰、稳定、可复用的 SOP(Standard Operating Procedure),以及基于 SOP 形成的 Workflow。

简单说一句就是:

模型负责输出,人负责把路铺好。


为什么越熟练,反而越需要 SOP

很多人在刚接触 Opencode 时,会觉得 SOP 是个 “新手才需要的东西”。
等自己用顺了,就开始随意对话、即兴发挥,哪里卡了就往模型里一股脑儿丢。

这种方式在小脚本、一次性需求里问题不大,
但只要项目稍微复杂一点,SOP 缺失的后果就会逐渐显现:

  • 模型对当前任务的理解不稳定
  • 不同轮对话输出风格和实现思路不一致
  • 修复一个问题,引入两个新问题
  • 过几轮之后,连模型自己都 “忘了现在在干嘛”

SOP 的存在,并不是为了限制模型的能力,而是为了减少不必要的理解分支
你提前告诉它当前阶段该做什么、不该做什么,反而能让模型把有限的注意力用在真正重要的地方。

从这个角度看,SOP 不是 “给模型用的”,
而是给人省心用的


SOP 和 Workflow,本质上解决的是两个问题

这里需要稍微区分一下这两个概念。

  • SOP 更像是一组 “约定俗成的操作规则”
    比如:每次开始前先让模型复述目标、禁止未经确认的大范围重构、输出必须标注修改点等。
  • Workflow 则是你在实际工作中形成的一种节奏
    什么时候该拆需求,什么时候该写代码,什么时候该停下来做确认。

SOP 偏静态,Workflow 偏动态;
一个负责 “别乱来”,一个负责 “往前走”。

在 Opencode + Oh My Opencode 的组合里,这两者往往是一起生效的。


我的 SOP

说明

  • 以下 SOP 均由 GPT5.2 模型给出并由本人在使用过程中润色改造
  • 所有内容均为可直接复制使用的工程指令
  • 不包含分析、解释或背景说明
    • 在开始使用 SOP 前应当完成项目初始化,推荐使用 Opencode 官方指令 /init
  • SOP 为原子级,Workflow 为高频组合
  • 指令中 ulw 命令为 oh my opencode 插件特有指令,详情可以移步 oh-my-opencode/README.zh-cn.md at dev · code-yeongyu/oh-my-opencode

SOP-01:工程启动 / 陌生任务分析

ulw #start
首先完整阅读 AGENTS.md,不要跳过。

然后按以下顺序执行分析:
1) 扫描整个仓库结构,识别项目入口、核心模块、分层与边界
2) 定位与当前任务直接相关的模块、文件和入口点
3) 用 3–5 条要点总结当前实现:
   - 每条描述一个职责或核心逻辑
   - 关注数据流、控制流和隐含假设
4) 标出不清晰、强耦合或潜在高风险区域
5) 提出至少 2 种可行推进方案:
   - 每种方案包含:优点、缺点、主要风险
6) 选择风险最低的方案,并给出明确的执行计划

约束:
- 在完成以上步骤前,不要修改任何代码
- 不要提前实现或重构


SOP-02:仓库探索 / 代码定位

ulw #scan
@explore:

1) 自顶向下扫描仓库结构
2) 识别与目标主题相关的模块、包和目录
3) 梳理主要调用链和依赖方向
4) 标出变更影响最大的 3 个区域并说明原因

约束:
- 仅做分析
- 不给实现或修改建议


SOP-03:技术调研 / 最佳实践

ulw #research
@librarian:

1) 查找该主题的官方文档或权威推荐做法
2) 对比 2–3 种成熟方案
3) 总结每种方案的适用场景与风险
4) 输出可直接用于当前项目的结论

约束:
- 避免长引用
- 只输出可执行结论


SOP-04:架构 / 方案决策

ulw #design
@oracle:

1) 在当前项目约束下提出 2–3 种设计或架构方案
2) 对每种方案说明:
   - 适用前提
   - 主要风险
   - 对现有代码的侵入程度
3) 推荐最安全的方案并说明原因
4) 明确不推荐其他方案的理由


SOP-05-FE:前端功能开发(使用 ui-ux-pro-max)

ulw #dev-fe
本任务是前端功能开发,加载skill:ui-ux-pro-max并根据这个技能的指引来进行 UI/UX 设计。

第 0 步:需求与界面范围确认
1) 用 3–6 条要点明确功能边界与用户路径
2) 列出必须态与异常态(loading / empty / error / 权限 / 网络失败)
3) 明确接口依赖与字段使用方式

第 1 步:UI/UX 设计(ui-ux-pro-max)
1) 产出页面结构与组件拆分
2) 明确交互细节与反馈机制
3) 定义各状态下的 UI 行为
4) 考虑可用性与可访问性

第 2 步:实现计划
1) 明确组件、路由、状态管理与请求层改动点
2) 拆分为可运行的增量步骤

第 3 步:增量实现
- 每一步完成后项目必须可运行
- 每一步完成后汇报:
  - 修改的文件
  - 实现的可观察行为
  - 修改原因
  - 下一步计划

约束:
- 优先复用现有组件与样式
- 不做顺手重构
- 避免过度抽象

完成标准:
1) 最小自测清单(主流程 + 异常态 + 边界态)
2) 核心交互演示路径
3) 接口字段与错误处理记录


SOP-06-BE:后端功能开发

ulw #dev-be
本任务是后端功能开发。

第 0 步:业务与契约确认
1) 明确输入、输出、业务规则、权限与幂等性
2) 列出数据模型变更与迁移策略
3) 明确错误码与异常语义

第 1 步:API 契约设计
1) 定义 endpoint、method、参数与响应结构
2) 明确分页、排序、过滤规则
3) 明确鉴权与资源所有权校验

第 2 步:测试准备
1) 补充最小测试或复现用例
2) 覆盖正常、异常、权限与边界情况

第 3 步:增量实现
- 每一步完成后服务必须可运行
- 每一步完成后汇报:
  - 修改的文件
  - 可验证结果
  - 修改原因
  - 下一步计划

约束:
- 优先复用现有架构与库
- 不做顺手重构
- 数据变更必须可回滚

完成标准:
1) 最小验证步骤
2) API 契约摘要
3) 影响范围说明


SOP-07:前后端对接 / 联调

ulw #dev-integration
本任务是前后端对接与联调。

第 0 步:冻结接口契约
1) 输出统一接口契约
2) 明确字段类型、必填性、默认值、空值语义
3) 明确错误码与错误结构
4) 明确鉴权方式与过期策略

第 1 步:联调准备
1) 后端提供可联调环境或 Mock
2) 前端完成请求封装与校验
3) 明确接口版本与兼容策略

第 2 步:联调闭环
1) 走通主流程
2) 覆盖异常场景(权限、参数、5xx、超时、空数据)
3) 明确 loading、重试、降级与幂等策略

第 3 步:对账与验收
1) 对账字段、单位、精度、时区
2) 对账分页、排序与筛选边界
3) 对账权限与越权访问

每一步完成后汇报:
- 修改的前后端文件
- 已打通的路径
- 当前阻塞点
- 下一步计划

完成标准:
1) 联调验收清单
2) 最终接口契约摘要
3) 最小复现链路


SOP-08:高风险重构(分析)

ulw #refactor
在修改任何代码前:
1) 找出所有相关定义与引用
2) 区分公开 API 与内部实现
3) 评估影响范围、风险点与迁移顺序

约束:
- 未确认前不要修改代码


SOP-09:高风险重构(执行)

ulw #refactor-exec
1) 如缺少测试,先补充
2) 按迁移顺序逐步修改
3) 每一步完成后说明:
   - 本步修改内容
   - 安全性理由
   - 可运行证据
   - 下一步计划


SOP-10:Bug / 异常排查

ulw #debug
1) 列出最可能的 3 个根因并排序
2) 为每个根因提供最小验证方法
3) 只修复被验证的根因
4) 说明其他假设被排除的原因

完成后:
- 给出回归验证步骤


SOP-11:提交前自检

ulw #review
1) 列出修改的文件与关键差异
2) 指出潜在风险与边界情况
3) 检查是否符合 AGENTS.md
4) 给出回滚方案
5) 给出最小本地验证命令


SOP-12:任务闭环

ulw #wrap
1) 确认目标是否完成
2) 按文件或模块总结关键变更
3) 列出风险与覆盖情况
4) 给出验证步骤
5) 给出回滚方案
6) 建议 AGENTS.md 的补充(仅新增)


关于 #xxx 这类指令

#xxx 是什么?
#start#plan#review 这一类 #xxx,本质上只是给模型看的阶段标签
它们不参与具体内容,只用来告诉模型:现在处在什么阶段,该怎么配合你工作


为什么要这样做?
因为模型默认会把所有对话当成一整段连续上下文来处理。
一旦讨论过多、来回修改几次,它就很容易把已经不重要、甚至已经作废的内容继续当成前提。

#xxx,等于是提前告诉模型:

“接下来换个阶段思考,别按刚才那套逻辑继续往下跑。”


这么做的必要性是什么?
这类指令的意义不在于 “让模型更聪明”,而在于减少跑偏和误解

在任务切换频繁、上下文偏长的情况下,一个清晰的阶段标记,往往比多写几段解释更有效。
成本很低,但能明显提升稳定性,尤其适合长期、反复使用。


SOP 之外,更重要的是 Workflow 的 “手感”

有了 SOP,并不代表事情就会自动变顺。
真正拉开差距的,往往是你如何把这些规则串成一套顺手的流程

很多人 Workflow 混乱,并不是能力问题,而是节奏问题:

  • 需求还没想清楚,就开始让模型写代码
  • 代码刚能跑,又急着往上堆功能
  • 出问题时,直接整段推翻重来

相比之下,一个成熟的 Workflow 往往更 “慢”,但更稳:

  • 先对齐理解,再动手
  • 先最小可用,再逐步完善
  • 每一步都能随时停下来回滚或修正

Oh My Opencode 在这里扮演的角色,其实很清晰:
它不是帮你 “多写点代码”,而是帮你守住流程的下限


我的 Workflow

# 接手陌生项目
ulw #start #scan

# 标准新功能(前后端)
ulw #start
→ ulw #dev-be
→ ulw #dev-fe
→ ulw #dev-integration
→ ulw #review
→ ulw #wrap

# 仅前端功能
ulw #start
→ ulw #dev-fe
→ ulw #review
→ ulw #wrap

# 仅后端功能
ulw #start
→ ulw #dev-be
→ ulw #review
→ ulw #wrap

# Bug 修复
ulw #debug
→ ulw #review
→ ulw #wrap

# 高风险重构
ulw #start
→ ulw #refactor
→ ulw #refactor-exec
→ ulw #review
→ ulw #wrap

# 技术选型
ulw #research
→ ulw #design


把模型当成同事,而不是许愿池

最后一个很重要的心态转变是:
不要把模型当成全知全能的存在,而要把它当成一个执行力强、但容易跑偏的同事。

你需要做的不是 “多问”,而是:

  • 把问题拆清楚
  • 把边界讲明白
  • 在关键节点做确认
  • 发现偏离及时拉回来

SOP 和 Workflow,本质上就是你和模型之间的协作协议。


结语

模型会更新,能力会上涨,也可能随时被限流;
但一套成熟的 SOP 和顺手的 Workflow,会长期存在。

Opencode + Oh My Opencode 真正提供的,不是 “写代码的捷径”,
而是一种在不完美模型条件下,依然能稳定推进工作的方式

当模型不再可靠时,
流程,才是你最后的安全绳。


出处与使用说明

本文内容及其中的 SOP / Workflow 由 @HappyCoding 原创整理并在 LinuxDo 首发。
允许在保留原始出处的前提下复制、修改与二次分发,
不建议在未注明来源的情况下直接商用。


📌 转载信息
原作者:
HappyCoding
转载时间:
2026/1/23 10:25:18

temporal 官网示例

python:

@workflow.defn
class SleepForDaysWorkflow:
    # Send an email every 30 days, for the year
    @workflow.run
    async def run(self) -> None:
        for i in range(12):
            # Activities have built-in support for timeouts and retries!
            await workflow.execute_activity(
                send_email,
                start_to_close_timeout=timedelta(seconds=10),
            )

            # Sleep for 30 days (yes, really)!
            await workflow.sleep(timedelta(days=30))

ruby:


# Send an email every 30 days, for the year
class SleepForDaysWorkflow < Temporalio::Workflow::Definition
  def execute
    12.times do
      # Activities have built-in support for timeouts and retries!
      Temporalio::Workflow.execute_activity(
        SendEmailActivity,
        start_to_close_timeout: 10
      )

      # Sleep for 30 days (yes, really)!
      Temporalio::Workflow.sleep(30 * 24 * 60 * 60)
    end
  end
end

C#:

[Workflow]
public class SleepForDaysWorkflow
{
    // Send an email every 30 days, for the year
    [WorkflowRun]
    public async Task RunAsync()
    {
        for (int i = 0; i < 12; i++)
        {
            // Activities have built-in support for timeouts and retries!
            await Workflow.ExecuteActivityAsync(
                (Activities act) => act.SendEmail(),
                new() { StartToCloseTimeout = TimeSpan.FromSeconds(10) });

            // Sleep for 30 days (yes, really)!
            await Workflow.DelayAsync(TimeSpan.FromDays(30));
        }
    }
}

PHP:

class SleepForDaysWorkflow implements SleepForDaysWorkflowInterface
{
  // Send an email every 30 days.
  public function sleepForDays(): void
  {
      for ($i = 0; $i < 12; $i++) {
          // Activities have timeouts, and will be retried by default!
          $this->sendEmailActivity->sendEmail();

          // Sleep for 30 days (yes, really)!
          Workflow::sleep(30 * 24 * 60 * 60)
      }
  }
}

感觉对于 java 程序员 php 的心智负担好小啊

temporal 官网示例

python:

@workflow.defn
class SleepForDaysWorkflow:
    # Send an email every 30 days, for the year
    @workflow.run
    async def run(self) -> None:
        for i in range(12):
            # Activities have built-in support for timeouts and retries!
            await workflow.execute_activity(
                send_email,
                start_to_close_timeout=timedelta(seconds=10),
            )

            # Sleep for 30 days (yes, really)!
            await workflow.sleep(timedelta(days=30))

ruby:


# Send an email every 30 days, for the year
class SleepForDaysWorkflow < Temporalio::Workflow::Definition
  def execute
    12.times do
      # Activities have built-in support for timeouts and retries!
      Temporalio::Workflow.execute_activity(
        SendEmailActivity,
        start_to_close_timeout: 10
      )

      # Sleep for 30 days (yes, really)!
      Temporalio::Workflow.sleep(30 * 24 * 60 * 60)
    end
  end
end

C#:

[Workflow]
public class SleepForDaysWorkflow
{
    // Send an email every 30 days, for the year
    [WorkflowRun]
    public async Task RunAsync()
    {
        for (int i = 0; i < 12; i++)
        {
            // Activities have built-in support for timeouts and retries!
            await Workflow.ExecuteActivityAsync(
                (Activities act) => act.SendEmail(),
                new() { StartToCloseTimeout = TimeSpan.FromSeconds(10) });

            // Sleep for 30 days (yes, really)!
            await Workflow.DelayAsync(TimeSpan.FromDays(30));
        }
    }
}

PHP:

class SleepForDaysWorkflow implements SleepForDaysWorkflowInterface
{
  // Send an email every 30 days.
  public function sleepForDays(): void
  {
      for ($i = 0; $i < 12; $i++) {
          // Activities have timeouts, and will be retried by default!
          $this->sendEmailActivity->sendEmail();

          // Sleep for 30 days (yes, really)!
          Workflow::sleep(30 * 24 * 60 * 60)
      }
  }
}

感觉对于 java 程序员 php 的心智负担好小啊