Computation 计算型域
算法不是状态,只存运行证据。
职责
| 负责 | 不负责 |
|---|---|
| 注册纯函数计算 | 分布式执行 |
| 同输入复用结果 | 算法血缘(只知道版本号) |
| 版本变更自动失效 | 结果过期(幂等键是永久的) |
| 运行历史与耗时 | 外部数据获取(应作为输入传入) |
为什么不是存储
定价、MRP、排程、成本分摊——这些是算法。真相是「输入 + 算法版本」,输出随时可重算。 把计算结果当真相存起来,就会腐烂成需要人工维护的数据。
所以这里存的是运行记录:
{ kind, version, inputHash, input, output, status, durationMs, actor, startedAt, finishedAt }三条规则
| # | 规则 | 实现 |
|---|---|---|
| 1 | 同输入 + 同算法版本 ⇒ 复用结果 | {kind, version, inputHash} 唯一索引 |
| 2 | 算法版本变更 ⇒ 自动失效旧缓存 | version 参与唯一键,无需清库 |
| 3 | 失败不占幂等键 | 部分索引只对 status='ok' 生效,失败可重试 |
第 3 条容易被忽略:如果失败也占唯一键,一次上游抖动会把该输入永久锁死。
纯函数约束
typescript
computation.register({
kind: 'pricing',
version: 1,
run: (input) => { /* 不要读库、不要调外部服务、不要看当前时间 */ },
});需要外部数据时把它作为 input 传进来(调用方负责取数)。 违反这条会让「同输入同输出」不成立,缓存就会返回错误结果。
接口
| Method | Path | 说明 |
|---|---|---|
| GET | /api/computation/kinds | 已注册类型与版本 |
| POST | /api/computation/:kind/run | 执行(带缓存与幂等) |
| GET | /api/computation/:kind/history | 运行历史 |
实际用途
参考案例用它算订单金额:
typescript
const pricingRun = await computation.run('pricing', { lines, discountBps: 0 }, actor);
// 定价结果与案例金额不一致 → 直接报错,避免两处口径漂移边界
- 无结果过期(要刷新就换 input 或加
force) - 无分布式执行(适合 CPU 轻量算法)
- 无算法血缘(只知道 version,不知道两个 version 差在哪)
源码
libs/modules/src/computation/
├── schemas/computation-run.schema.ts 运行记录 + 部分唯一索引
├── services/computation.service.ts register / run / history
└── computation.controller.ts 3 个端点