定义routine
routine 是 Oak 中“在应用启动或停止时执行一次”的例程。
它和 watcher、timer 的区别在于:
watcher:周期轮询;timer:cron 调度;routine:只在启动或停止阶段执行一次。
因此,routine 非常适合处理这类工作:
- 启动时建立外部连接;
- 启动时初始化某些内存结构或索引;
- 停止前释放资源;
- 停止前做收尾动作。
routine 写在哪里
Oak 约定把 routine 分成两类文件:
src/routines/start.tssrc/routines/stop.ts
oak-backend-base/src/AppLoader.ts 会分别在应用启动和停止时调用:
execStartRoutines()execStopRoutines()
routine 的类型
oak-domain/src/types/Timer.ts 中定义:
type Routine = FreeRoutine | Watcher;
也就是说,routine 既可以写成自由函数式的启动例程,也可以直接复用 watcher 风格。
FreeRoutine
这种形式最适合做纯初始化或纯收尾逻辑:
{
name: '初始化mqtt客户端连接',
routine: async (context, env) => {
init(env.contextBuilder);
return context.opResult;
},
}
这里的 env 是 RuntimeRoutineEnv,当前包含:
socketcontextBuilderregisterTrigger/unregisterTriggerregisterChecker/unregisterCheckerregisterWatcher/unregisterWatcherregisterTimer/unregisterTimer/rescheduleTimer
所以 routine 不仅能拿到当前 context,还能管理应用运行期间动态加入的规则和后台任务。例如,启动 routine 可以根据数据库状态注册 timer,停止 routine 再注销对应任务。动态注册的对象仍须符合各自完整类型,不能通过补充未声明字段改变 loader 行为。
Watcher 风格 routine
如果你要在应用启动时,针对某类实体先扫一遍数据再处理,也可以直接写成 watcher 风格。
bm-smart/src/routines/start.ts 中就有这样一个例子:
{
name: '初始化所有系统的Shell的Exception向量化索引',
entity: 'system',
projection: systemProjectionForFaissSync,
filter: {},
type: 'base',
fn: syncExceptionVectors,
}
这说明 routine 不是只能做“无数据上下文”的初始化,它同样可以利用 Oak 已有的实体扫描与处理能力。
Watcher 风格 routine 复用完整的 Watcher 联合类型,因此既可以是直接 operate 的 BBWatcher,也可以是 WBWatcher / WBFreeWatcher,并不固定为“先查询再调用普通 context fn”。
一个真实的启动/停止例子
bm-smart 里的 start/stop routines 很适合作为理解样板:
启动
{
name: '初始化mqtt客户端连接',
routine: async (context, env) => {
init(env.contextBuilder);
return context.opResult;
},
}
停止
{
name: '关闭mqtt客户端连接',
routine: async (context, env) => {
await dispose();
return context.opResult;
},
}
从这个例子你会发现,routine 最擅长的其实就是“把系统外围资源的生命周期,接到 Oak 应用生命周期上”。
AppLoader 会串行执行 start/stop routine。自由函数式 routine 由 loader 创建 context,成功后提交、失败后回滚;Watcher 风格 routine 使用 watcher 自己的事务路径。任意一项失败都会继续向上抛出,而不是记录后忽略,因此启动例程失败会阻止正常启动流程继续完成,停止例程也不应假定后续项一定执行。
routine 应该做什么,不该做什么
适合写进 routine 的事情:
- 初始化 MQTT、消息队列、外部 SDK;
- 预热缓存;
- 建立长连接;
- 做一次启动时的全量扫描;
- 停止前释放连接或落盘。
不太适合写进 routine 的事情:
- 需要反复执行的任务;
- 必须按 cron 定时执行的任务;
- 每次业务操作都应触发的规则。
换句话说,routine 解决的是应用生命周期问题,而不是业务动作问题。
一个经验
只要你在想“这段逻辑应该在服务启动时主动跑一次”,就应该先想到 routine。
这比把初始化逻辑散落到各种 feature、aspect 或全局脚本里,要更符合 Oak 本身的运行模型。