六 · 动力学层:从名词到动词
动作、门控写入、状态所有权、预期值
只描述「有什么」的本体,用完一次就过期。真正被业务接受的本体,都长出了动词:它不只说明对象之间存在什么关系,还规定允许对它们做什么、什么条件下允许、做完之后世界变成什么样。这一章讲这一层的设计。
为什么名词层不够
举一个具体的失灵过程。第一版模型里有「客户」「产品」「持仓」「风险等级」四类对象和它们的关系,能回答「谁持有多少什么」。业务用了一周,提出第一个真实需求:
「客户风险等级上调时,能不能自动把超出其风险偏好的持仓标出来,并生成一份待确认清单?」
这个需求里有三样东西名词层给不了:一个动作(上调等级)、一个触发条件(超出偏好)、一个副作用(生成清单)。如果模型里没有动作这个概念,实现方式只能是:某段代码改数据库 → 另一个定时任务扫全表 → 再发一封邮件。这三步之间没有事务、没有权限、没有审计,出错时无法定位。
动作层的价值就在这里:它把「一次会改变世界的操作」变成一个有名字、有参数、有前置条件、有审计记录的东西。
动作的六个组成部分
一个动作声明至少要写清楚六件事:
| 组成 | 说明 | 常见缺失 |
|---|---|---|
| 参数 | 输入什么,类型与取值约束 | 参数校验散落在代码里 |
| 目标 | 作用于哪些对象(单个 / 集合) | 集合操作没有数量上限 |
| 前置条件 | 什么条件下才允许执行 | 写成自然语言注释 |
| 副作用 | 会写哪些对象、哪些链接、哪些派生值 | 副作用不声明,靠读代码猜 |
| 权限 | 谁可以执行,依据什么策略 | 权限与动作分离,无法审计 |
| 结果 | 成功返回什么,失败返回什么 | 失败只返回一句「操作失败」 |
其中前置条件是最关键的一项,因为它把校验和动作连了起来:前置条件就是一组可执行的约束,作用于这次操作涉及的对象。这样「拒绝一次非法写入」这件事就不需要额外的机制——它是动作执行流程的天然一部分。
副作用的声明同样重要,因为它是影响面分析和回滚的基础。如果一次动作会写五个对象却只声明了两个,那么下游的血缘、缓存、派生指标全部会错。
状态所有权:三类数据,三种待遇
这是整个动力学层最实用的一张表。任何一个字段,先归类,再决定谁能写。
| 类别 | 含义 | 谁写 | 冲突处理 |
|---|---|---|---|
| 源系统所有(source-backed) | 权威值在外部系统(核心、ERP、CRM) | 只能由源系统写,本体只读 | 本体不覆盖;冲突时以源为准,差异进对账队列 |
| 本体所有(ontology-owned) | 只在本体里存在的语义(标签、分组、策略绑定) | 只能由动作写 | 按动作的权限与版本 |
| 派生(derived) | 由前两类计算得出(风险分、敞口、评级) | 不允许任何直接写入 | 只能重算;重算必须可追溯到输入版本 |
这张表能消掉大量争论。例如「客户等级到底以哪个系统为准」这个问题,答案不是开会讨论出来的,而是先归类:如果等级在核心系统里是权威的,那它就是 source-backed,本体里的等级只是镜像加语义;如果本体需要承载「内部评级」,那它是 ontology-owned,必须走动作写、必须留审计。
最常见的架构错误是把派生值当 source-backed:某个风险分被写进了本体,然后三处代码各自更新它,最后没有任何人知道当前值是怎么来的。正确做法是它只能是 derived,只能由一次重算产生,且重算记录输入的数据版本。
写回权威源:三种模式
本体不是新的真相源,它要把变更写回去。三种模式,按风险从低到高:
一、只读镜像(无写回)。 本体定期同步源系统,所有变更回到源系统里做。最简单、最安全,代价是动作层的能力受限——你只能做「读 + 标记」,不能做「改」。
二、动作触发外部调用(推荐起步)。 动作在本体里声明,执行时调用源系统的 API 完成实际写入,本体记录动作日志与结果。写回是同步的、有事务边界(至少是补偿事务)。这个模式的好处是:真相源不变,本体只是加了一层「谁能改、怎么改、改过什么」。
三、本体为主、异步回写(风险最高)。 本体先写自己的库,再异步同步到源系统。它带来最好的用户体验(快、可离线),也带来最难处理的问题:回写失败怎么办、两边冲突以谁为准、用户看到的和源系统里的不一致时怎么解释。
推断:从模式二开始。 模式三只有在源系统无法提供写接口、或写延迟不可接受时才考虑,且必须配一个显式的「待回写」状态和对账任务。
预期值与对账
动作执行之后,世界应该变成什么样?这件事必须被声明,否则无法验证。
做法是给每个动作声明后置条件(postcondition):执行成功后,某些断言必须为真。例如「上调风险等级」的后置条件是「该客户的等级等于目标值,且变更记录存在,且关联的持仓标记任务已入队」。
后置条件带来两个能力:
- 幂等性可验证:同一个动作执行两次,后置条件仍然成立,说明它是幂等的。
- 对账可自动化:定期抽样执行过的动作,重新检查后置条件,发现不一致就告警。
对账是这一层最被低估的机制。它不需要复杂技术,只需要一个定时任务去问:「上周执行的那 300 次动作,现在回头看,世界还是它们承诺的样子吗?」这个问题的答案,通常会在第一次运行时给出几个惊喜。
审计:五个必须记录的字段
一次动作的审计记录至少要有:
- 谁(主体,含其权限来源)
- 何时(执行时间,以及策略生效的时间——两者可能不同)
- 做了什么(动作 ID + 参数)
- 为什么(业务理由,通常是一个必填的自由文本或工单号)
- 结果(成功/失败、写了哪些对象、触发了哪些副作用)
其中「为什么」最容易被省,而它恰恰是事后追责与复盘唯一有用的字段。把它设成必填,会显著降低随意操作的数量。
反模式:把动作做成 CRUD API
最常见的退化形态:所谓「动作」只是 POST /objects/{id} 加一个字段名。它有四个症状:
- 前置条件为空,任何值都能写。
- 副作用不声明,写了什么要看代码。
- 权限在网关层统一判,与「这个动作该不该被这个人执行」无关。
- 没有后置条件,无法验证执行结果。
这样的系统能跑,但它退化成了一个带权限的数据库客户端,动力学层的能力一点没拿到。
判断一个系统有没有动力学层,最快的办法是问:给我看一个被拒绝的动作,以及拒绝的理由。 如果拒绝的理由是「权限不足」或「参数不合法」,那只是接口校验;如果理由是「该客户存在未结清的头寸,不允许降级」,那才是业务规则在动作上生效。
与本体其他层的关系
动力学层不是替代名词层,它是名词层的消费者:
- 动作的参数与目标,引用名词层的对象类型。
- 前置条件引用名词层的约束(第三章的 L2 层)。
- 副作用写的是名词层的对象与链接。
- 权限引用名词层的组织与角色。
所以顺序是:先把名词层的最小集合建好(12–18 个类型、5 条必须为真的约束),再挑一个高频、边界清晰的业务动作做成第一个动力学闭环。 不要一次建十个动作——第一个动作会暴露名词层 80% 的建模错误,改完再扩展成本最低。