Software Architect · 模块 08

代码一个季度就能重写一遍。但数据、契约和历史上的错误,有时候会陪着产品走完它整段生命。

Invariants · normalization · migration · OLTP · OLAP

§ 01

数据模型要保护业务的语义,而不只是方便地服务当前这一屏。

数据老得比代码慢

装修可以很快重做,地基一旦浇错,就会陪着这栋房子很久。

框架、UI 甚至编程语言都可以换。但表、事件、key、历史记录、对外报表会继续活着。所以 data model 是最昂贵的几类架构决策之一。

架构师必须明确地说出 invariants——那些必须永远为真的事。比如:order 不能在没有 payment intent 的情况下被标为已支付;ledger entry 不能凭空消失;balance 不能在没有授权 overdraft 的情况下变成负数。

Normalization 与 denormalization 不是教条

档案室保存原件,展厅做副本供人快速浏览。两种形式都需要,只是服务的目的不同。

Normalization 降低冗余和数据漂移的风险。Denormalization 加快读取,让 read model 更简单。OLTP 系统关心事务和当前状态的一致性;OLAP 系统关心分析查询、历史和聚合。

错误的做法,是想用同一个模型同时完美服务 checkout、realtime dashboard 和财务报表。多数情况下你需要一个 source-of-truth 模型,加上独立的 projection / read models。

§ 02

好的模型让不可能的状态真的不可能出现,或者至少在系统里显式地非法。

正例:order 上的 pricing snapshot

商店的收据记录的是购买当时的价格,哪怕第二天商品涨价了。

Order 不只保存 product_id,还保存购买当时的价格、币种、税、折扣、商品名快照。Catalog 之后可以变化,但历史 order 仍然是正确的。

这是一种很小的 denormalization,却保住了业务语义。没有它,报表和客服都会被当前 catalog 的状态绑架。

反例:万能的 events_json 表

一个没有货架的仓库看起来很灵活,什么都能放;然后谁也找不到自己要的东西。

团队把所有业务实体一股脑塞进一张带 JSON 列的表,因为"这样更灵活"。一开始确实快。然后开始出现 JSON 内部的迁移、藏在代码里的隐式 schema、慢查询、缺失的 constraints,以及没人能判断哪条数据是合法的。

JSON 适合用来承载可扩展的属性和事件,但不该在有严格 invariants 的地方替代真正的模型。

自查
  • 哪些 invariants 由数据库保护,哪些只靠代码? - 哪些数据是 source of truth? - 字典表变化时,历史数据会怎样? - 这个模型的安全迁移路径是什么样的?