本篇要回答的问题:架构里最难啃的骨头——全局性功能(读盘存盘、Undo/Redo、宏录制)该怎么设计?它们很难剥离成独立模块,能不能也做正交分解?
上一讲我们用 IO 子系统演示了"不断重新审视边界",并引出"全局性功能"概念。本讲专门攻坚这类难题:它们的共同特征是每增加一个功能,都要回过头去考虑这个功能怎么处理,所以难以剥离。但许式伟亮出架构师的信仰——任何功能都可以正交分解,没找到方法只是因为还没透彻理解需求。
| 功能 | 为何全局 |
|---|---|
| 读盘/存盘 | 每加一个功能都要考虑其数据如何存盘/恢复 |
| Undo/Redo | 每加一个功能都要考虑如何回滚/重做 |
| 宏录制 | 每加一个功能都要考虑结果如何用 API 表达、界面操作如何翻译成 API |
反例对照:服务端 API 鉴权、记录日志看似全局,但能在入口统一处理或只提供辅助函数,心智负担低,不算真正的全局性功能。
读盘/存盘:用 IO DOM 反向依赖
承接 60 讲的结论,关键有两点:
| 关键点 | 说明 |
|---|---|
| 为什么必须独立子系统 | 读盘存盘需求发散(格式越来越多) |
| 为什么稳定依赖设计为 IO DOM | DOM 是核心系统的常规界面;IO DOM 是其子集,只是个归类,并非为 IO 定制 |
关键判断:全局性功能通常会带来一套复杂框架(实现一个库 + 一套使用机制)。IO DOM 反其道而行之——通过抽象核心系统的接口,让全局功能反向依赖这些接口。这样虽不能消除全局性,但能把核心系统的伤害值削弱到近乎为零。
Undo/Redo:不在问题发生处解决
| 方案 | 机制 | 评价 |
|---|---|---|
| Command 模式(设计模式经典) | 每个操作实现为 Command,并实现反操作;框架维护 Command 队列 | 框架只省 1% 工作量,99% 在实现一个个 Command,心智负担极大 |
| 多版本/数据层方案(许式伟) | 每次编辑自动"快速存盘"形成多版本,Undo 即回退到上一版本 | 与核心系统解耦 |
灵感来源于做 IO 子系统的经历——它们的共同点是都和数据本身密切相关:
| 启发来源 | 机制 | 关联能力 |
|---|---|---|
| 快速存盘 | 只把增量追加到文件尾部,形成多版本 | 多版本 → 镜像 → Undo/Redo |
| 异步存盘/打印 | 对 DOM 建 Snapshot 镜像,后台存盘/打印不阻塞交互 | 异步操作、异步打印 |
洞见:只要支持了多版本,就有了镜像能力,也就有了 Undo/Redo 能力。这促成了 数据层(DataLayer) 的诞生——类比服务端的数据库,是托管所有数据的存储中间件。
| 数据层的好处 | 数据层的坏处 |
|---|---|
| 异步操作、Undo/Redo 都不是问题,还有更多想象空间 | 对 Model 层业务逻辑有侵入(内存数据结构 vs 数据库,体验差异大),需尽量隐藏差异 |
关键判断:Undo/Redo 的解法并不在问题发生的地方解决——这正是需求分析的复杂性所在。随着 SaaS 大行其道,基于存储中间件写业务逻辑已是必然趋势。
宏录制:和服务端日志很像
宏(Macro)= 二次开发代码(微软几乎所有产品都有 API 层,如 Office、Visual Studio)。宏录制 = 把界面操作用 API 调用记录下来,变成一段二次开发代码。
| 宏录制的好处 | 说明 |
|---|---|
| 可反复重放 | 甚至指定快捷键——用户竟能给系统添加新功能 |
| 可修改迭代 | 帮二次开发新手学习 API,大幅降低入门门槛 |
| 与服务端日志的对比 | 相同点 | 不同点 |
|---|---|---|
| 机制 | 都记录一段文本(若 DOM API 基于 RESTful,可在 API 入口实现录制) | 宏录制需考虑 API 嵌套——只录最外层 API,不录内部调用 |
洞见:宏录制相比存盘读盘、Undo/Redo,是侵略性最小、心智负担最低的全局功能。
架构师的信仰
核心信念:任何功能都是可以正交分解的,即使我目前还没有找到方法,那也是因为我还没有透彻理解需求。
| 业务分解的公式 | 说明 |
|---|---|
| 业务 = 最小化核心系统 + 多个正交分解的周边系统 | 核心系统一定要最小化、稳定 |
| 坚持不往核心加新功能 | 这样业务架构就不可能有臭味 |
关键判断:模块演化中会经历剧烈调整期(源于需求理解深化、引发对接口的反思)。无论如何——保持核心系统的纯洁性比什么都重要。
总结
架构分解有两大难题:①需求的交织(全局性功能);②需求的易变(场景发散)。本讲用读盘存盘(IO DOM 反向依赖)、Undo/Redo(多版本/数据层)、宏录制(API 入口录制)三个例子,证明全局功能也能正交分解。
先把"是什么"回答清楚
| 概念 | 一句话说明 |
|---|---|
| 全局性功能 | 每加新功能都要回头处理、难以剥离的功能 |
| IO DOM 反向依赖 | 让全局功能反向依赖核心接口,伤害值近零 |
| 数据层(DataLayer) | 托管所有数据的存储中间件,支持多版本/Undo/异步 |
| 快速存盘 | 只追加增量到文件尾,形成多版本 |
| 宏录制 | 把界面操作记录为 API 调用,只录最外层 |
| 架构师的信仰 | 任何功能都可正交分解,没找到只因需求没吃透 |
一句话速记
啃下全局性功能的钥匙是"换地方解决"——读盘存盘靠 IO DOM 反向依赖、Undo/Redo 靠多版本数据层、宏录制靠 API 入口;而一切的底线是保持核心系统的纯洁性。
几条值得记住的判断
- 全局功能别给核心建框架,而要让它反向依赖核心接口(IO DOM)。
- Undo/Redo 不在问题发生处解决——多版本即镜像即 Undo。
- 三类全局功能侵略性排序:存盘读盘 ≈ Undo/Redo > 宏录制。
- 架构师的信仰:任何功能都可正交分解;核心系统最小化、保纯洁。
思考题
许式伟解决 Undo/Redo 的方式"不在问题发生处解决"——绕到数据层用多版本实现。回到你自己的系统:有没有一个"全局性功能"(如审计日志、软删除、权限校验)目前是用一套框架硬塞进核心的?试着想想,它背后是否也和"数据本身"密切相关、能否绕到数据层去统一解决?

