Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

定义routine

routine 是 Oak 中“在应用启动或停止时执行一次”的例程。

它和 watchertimer 的区别在于:

  • watcher:周期轮询;
  • timer:cron 调度;
  • routine:只在启动或停止阶段执行一次。

因此,routine 非常适合处理这类工作:

  • 启动时建立外部连接;
  • 启动时初始化某些内存结构或索引;
  • 停止前释放资源;
  • 停止前做收尾动作。

routine 写在哪里

Oak 约定把 routine 分成两类文件:

  • src/routines/start.ts
  • src/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;
    },
}

这里的 envRuntimeRoutineEnv,当前包含:

  • socket
  • contextBuilder
  • registerTrigger / unregisterTrigger
  • registerChecker / unregisterChecker
  • registerWatcher / unregisterWatcher
  • registerTimer / 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。

这比把初始化逻辑散落到各种 featureaspect 或全局脚本里,要更符合 Oak 本身的运行模型。