实战:用 Cordis 组装一个微应用

专栏:Cordis 插件框架 · 第 12 / 12 篇
Cordis实战

:::info 学习目标 完成本篇后你能够:不借助脚手架,用 Cordis 组装一个涵盖配置、服务、事件、生命周期全部机制的微应用;用 dsh 的眼光审视自己的设计。 前置:第 211 篇完成。预计时长:23 小时。 :::

毕业项目:一个”定时提醒服务”——按配置轮询任务、提供 HTTP 查询、事件驱动日志。麻雀虽小,五个机制全用到。

项目结构

reminder/
├─ cordis.yml              # 配置树(第 11 篇的 Loader 直接可用)
├─ package.json            # { "type": "module", "dependencies": { "cordis": "^4" } }
└─ src/
   ├─ clock.ts             # 插件 1:提供时钟服务
   ├─ store.ts             # 插件 2:提供任务存储(inject: clock)
   └─ main.ts              # 入口:挂载 + 优雅关机
# cordis.yml —— 配置树(Loader 的条目格式)
- id: clock
  name: ./src/clock.ts
  config:
    intervalMs: 1000
- id: store
  name: ./src/store.ts
- id: http
  name: ./src/http.ts
  inject: [store]

三个插件

时钟服务(Service 基类 + effect 定时器):

// src/clock.ts
import { Service, Context } from 'cordis'
import { z } from 'zod'

export const Config = z.object({ intervalMs: z.number().default(1000) })

export class Clock extends Service {
  static Config = Config          // 配置校验:启动前自动执行(第 2 篇)
  constructor(ctx: Context, config: z.infer<typeof Config>) {
    super(ctx, 'clock')
    ctx.effect(() => {
      const timer = setInterval(() => this.ctx.emit('clock/tick', this.now()), config.intervalMs)
      return () => clearInterval(timer)
    }, 'clock.timer')
  }
  now() { return new Date().toISOString() }
}

任务存储(inject 时钟,任务到期发事件):

// src/store.ts
import { Context } from 'cordis'
import type { Clock } from './clock.ts'

export const inject = ['clock']

export function apply(ctx: Context) {
  const clock = ctx.clock as Clock
  const tasks: { due: number; label: string; done: boolean }[] = []
  ctx.provide('tasks', {
    add(label: string, dueMs: number) {
      tasks.push({ due: Date.now() + dueMs, label, done: false })
    },
    list() { return [...tasks] },
  })
  ctx.effect(() => ctx.on('clock/tick', () => {
    for (const t of tasks) {
      if (!t.done && Date.now() >= t.due) {
        t.done = true
        ctx.emit('task/due', t.label)          // 到期 → 发事件,日志插件来订阅
      }
    }
  }))
}

组装与优雅关机(main.ts):new Context() → 挂载三个插件 → 注册 HTTP 查询 → 信号处理里 dispose

图表(cordis-capstone.md)

验收标准(全部通过即毕业)

  1. npx cordis(或自己的入口)启动后,添加一个 5 秒后的任务。验收:到期时看到 task/due 的日志;
  2. cordis.yml 里把 intervalMs 改成 2000 并保存。验收:无需重启,tick 间隔变化(配置热重载生效);
  3. kill <pid>。验收:日志显示三个插件按序卸载、进程退出码 0;
  4. 把 store 插件的 inject 临时删掉并故意在 apply 第一行读 ctx.clock。验收:报”服务不存在”——体会门控的价值后改回来。

对照检查:你的实现 vs dsh 的用法

你的实现dsh 里的对应物差距在哪
cordis.yml + Loader162 行配置树 + patch 层叠dsh 多了层级优先级与热重载的 diff 更新
provide('tasks')ctx.provide + Service 类 + 声明合并dsh 多了类型合并与作用域
ctx.on('clock/tick')ctx.on + @mode 标注dsh 多了作用域过滤与五种分发模式
process.on('SIGTERM')runProfile 的关机序列dsh 多了 fiber 级联卸载与超时分级

差距清单的每一行都对应 dsh 专栏的一篇——现在你可以带着这张表进入 DeepSeek Harness 专栏,读它的体验将是不断的”原来如此”

结语

Cordis 专栏到此收官。回看学习路径:从第一个插件到 fiber 状态机,你掌握的不是”一个库的 API”,而是插件化系统的一整套设计语言——服务、事件、副作用、生命周期、作用域。这套语言在 dsh 里说得最完整,也会在你自己的下一个项目里继续被使用。

← 返回文章列表