MemoFlow · Schedule / Scheduler 开发笔记
按版本记录用户日历规划与后台可靠调度从统一构想到职责分离的过程。
Schedule 模块目标
我希望 MemoFlow 能在日历里展示 Goal、Task 和提醒,也能在时间到达时真正触发业务事件。
这听上去都与“时间”有关,但其实包含两个能力:安排并展示日程,以及在未来某个时刻可靠执行。第一版先把它们作为同一个 Schedule 方向讨论。
V1.0.0
先考虑做一个通用 ScheduleTask:Goal、Task、Reminder 提交触发时间和 Payload,Schedule 在后台保存计划并等待执行,时间到了再发布事件给业务模块或 Notification。
计划支持一次性时间和 Cron 重复规则,能暂停、恢复和删除。比如一个 Reminder 被关闭,就要暂停相应的 ScheduleTask;重新启用时再计算下一次触发。
Cron 适合“每周一九点”之类的固定重复,但无法单独表达所有业务时间语义:有时需要一个确切时间点,有时是日历区间,还有时是依赖设备正在使用的时长。所以先把触发配置与业务 Payload 分开,不让 ScheduleTask 理解每种业务对象的所有字段。
v1.1.0
仅在内存里创建定时器还不够。进程退出、服务重启或消费者暂时失败之后,提醒不能因为计时器丢失就彻底消失。
准备给后台执行增加持久化状态:下一次运行时间、执行尝试、重试次数,以及并发执行时的占用标记。处理失败要区分是否可以重试,避免同一个计划被多个 Worker 同时执行。
这里要先想清楚两个边界:成功唤醒并不等于业务动作完成;重试也不能产生两次相同副作用。执行请求需要稳定的身份与幂等约束,由实际业务处理者决定一次动作是否已经生效。
V2.0.0
现在准备把 Planner / Calendar 和 Scheduler 正式分开。
日历用户想知道“这一周安排了什么、哪里冲突”;后台 Scheduler 想知道“哪个 Handler 什么时候唤醒、失败怎样恢复”。同一条 Task 可能需要出现在日历中,也可能产生后台触发,但它们不是同一个对象。
- Schedule / Planner: 管 CalendarEntry、日/周/月视图、时间块编辑和冲突检测,从 Task、Goal、Routine 读取需要展示的时间信息。
- Scheduler / Temporal Engine: 管 ScheduledInvocation、InvocationAttempt、lease、队列、重试与恢复,只对可靠执行负责。
不再由业务模块直接创建 Scheduler 的数据库记录。Goal、Task、Routine 只提交属于自己的 ScheduledIntent;统一的 SchedulingPort 负责 reconciliation,将业务意图投影为可执行的 Invocation。
v2.1.0
开始完善跨模块的时间视图。CalendarEntry 是日历自身管理的事项;TaskOccurrence、Goal 的目标时间和 Routine 的计划提醒由各自 Owner 提供只读投影,Planner 将它们组合显示。
用户在日历里拖动一个 Task 时间块时,应调用 Task 的修改命令,不能只改画在日历上的临时条目;对暂时没有单次重排能力的 Routine,可以先只读显示,不擅自修改它的触发规则。
Scheduler 继续作为独立的可靠执行层,产品界面不提供直接暂停、删除原始 Invocation 的操作。至于采用自研 Worker 还是 pg-boss 一类成熟方案,需要比较事务入队、租约、恢复、部署环境与桌面本地运行要求,再决定是否迁移。