EnriqueMark
设计模式与架构

数据导向型的模块设计

2026-04 简体中文

我现在发现,与其将各种状态都耦合在代码内部,不如用资料库做统一的、共用的持久状态管理。这样各个功能模块彼此之间就可以去耦合,而以模块——抽象的单位积木——为存在彼此只关注数据,而非在内部进行复杂的状态管理和交互。

这种做法在跨模块共享状态的时候尤其好用,各个模块只需要专注于自己的功能实现,而不必考虑外部的状态。当状态需要更新时,直接朝专门的状态管理模块发送请求即可,状态的获取和更改都可以集中管理。在项目初期,可以自行修改资料库字段的前提下,这样的数据导向型设计可以让所有功能模块的状态同步省下很多功夫。持久化的状态保存当然也可以存在内存或磁盘中,但这样就会吃不到资料库定期备份和统一管理的优势。

当然,这个做法不是没有代价,IO会增加,对资料库的压力也会增加。但对于小规模、并发数不高的内部软件来说,这样的设计完全够用了。除非有明确的即时性或IO不便的离线场合,否则用资料库来存状态是个蛮不错的设计。

除此之外,以上方案实际上并未彻底的将耦合去除——这有点像是熵的增减,并非封闭系统的功能代码将自身的熵排放了出去,转移给了数据库。那也就意味著数据库查承担了全部的状态管理功能,一旦发生变动,这里的改变是连锁的。比如增加或者减少字段,导致整体都出问题。这里的连锁依赖实际上和组合和继承里面说过的类的问题差不多,当父类更改,子类依赖的某个关键要素没了,直接报错。而且这是在构建父类时不可预知的。同样,资料库会被谁、在什么时候修改,也是我作为代码构建者不可预知的,甚至哪天我自己改了什么东西都不好说。

因此,为了增强整体的稳健性,在数据库和功能模块中间增加一个抽象的接口更好,让功能模块对接口负责,而不是对数据库直接负责。然后再用专门的工具去维护这个接口,将资料库的内容装填好——这里有点像是工厂模式的变体,只是从处理对象变成了处理数据。底层的积木只对接口负责在去耦合的思想上和DI是一致的,即控制反转,数据怎么处置被外放,而功能模块自身只关注接口或是说结构。就像上述所说,这里的问题几乎和继承一模一样,因此解决方案也是殊途同归。

再者,依赖数据库的另一个问题就是竞态条件,并发修改的问题是一切外部状态都可能出现的,如果不加锁,这里就会导致依赖上的广播错误:A收到的信息是A,但其实B已经把它改成B了。数据库更适合存的是原子化和持久化的状态,而可能快速变更的短期状态,可能不适合放在上面。